File Coverage

blib/lib/Perlbal/Test/WebClient.pm
Criterion Covered Total %
statement 100 114 87.7
branch 40 58 68.9
condition 12 25 48.0
subroutine 14 15 93.3
pod 0 8 0.0
total 166 220 75.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Perlbal::Test::WebClient;
4              
5 57     57   3798 use strict;
  57         107  
  57         2093  
6 57     57   292 use IO::Socket::INET;
  57         85  
  57         556  
7 57     57   63707 use Perlbal::Test;
  57         107  
  57         5854  
8 57     57   314 use HTTP::Response;
  57         86  
  57         1381  
9 57     57   290 use Socket qw(MSG_NOSIGNAL IPPROTO_TCP TCP_NODELAY SOL_SOCKET);
  57         106  
  57         4568  
10              
11             require Exporter;
12 57     57   293 use vars qw(@ISA @EXPORT $FLAG_NOSIGNAL);
  57         76  
  57         79231  
13             @ISA = qw(Exporter);
14             @EXPORT = qw(new);
15              
16             $FLAG_NOSIGNAL = 0;
17             eval { $FLAG_NOSIGNAL = MSG_NOSIGNAL; };
18              
19             # create a blank object
20             sub new {
21 11     11 0 6428 my $class = shift;
22 11         40 my $self = {};
23 11         124 bless $self, $class;
24 11         66 return $self;
25             }
26              
27             # get/set what server we should be testing; "ip:port" generally
28             sub server {
29 11     11 0 119 my $self = shift;
30 11 50       95 if (@_) {
31 11         184 $self->{_sock} = undef;
32 11         124 return $self->{server} = shift;
33             } else {
34 0         0 return $self->{server};
35             }
36             }
37              
38             # get/set what hostname we send with requests
39             sub host {
40 0     0 0 0 my $self = shift;
41 0 0       0 if (@_) {
42 0         0 $self->{_sock} = undef;
43 0         0 return $self->{host} = shift;
44             } else {
45 0         0 return $self->{host};
46             }
47             }
48              
49             # set which HTTP version to emulate; specify '1.0' or '1.1'
50             sub http_version {
51 11     11 0 66 my $self = shift;
52 11 50       59 if (@_) {
53 11         138 return $self->{http_version} = shift;
54             } else {
55 0         0 return $self->{http_version};
56             }
57             }
58              
59             # set on or off to enable or disable persistent connection
60             sub keepalive {
61 13     13 0 45007 my $self = shift;
62 13 50       77 if (@_) {
63 13 100       127 $self->{keepalive} = shift() ? 1 : 0;
64             }
65 13         63 return $self->{keepalive};
66             }
67              
68             # construct and send a request
69             sub request {
70 136     136 0 131709 my $self = shift;
71 136 50       624 return undef unless $self->{server};
72              
73 136 100       656 my $opts = ref $_[0] eq "HASH" ? shift : {};
74 136         1065 my $opt_headers = delete $opts->{'headers'};
75 136         5100 my $opt_host = delete $opts->{'host'};
76 136         337 my $opt_method = delete $opts->{'method'};
77 136         320 my $opt_content = delete $opts->{'content'};
78 136         339 my $opt_extra_rn = delete $opts->{'extra_rn'};
79 136         337 my $opt_return_reader = delete $opts->{'return_reader'};
80 136         397 my $opt_post_header_pause = delete $opts->{'post_header_pause'};
81 136 50       1335 die "Bogus options: " . join(", ", keys %$opts) if %$opts;
82              
83 136         355 my $cmds = join(',', map { eurl($_) } @_);
  138         1336  
84 136 50       474 return undef unless $cmds;
85              
86             # keep-alive header if 1.0, also means add content-length header
87 136         308 my $headers = '';
88 136 100       533 if ($self->{keepalive}) {
89 131         1778 $headers .= "Connection: keep-alive\r\n";
90             } else {
91 5         21 $headers .= "Connection: close\r\n";
92             }
93              
94 136 100       392 if ($opt_headers) {
95 5         13 $headers .= $opt_headers;
96             }
97              
98 136 100 66     1517 if (my $hostname = $opt_host || $self->{host}) {
99 17         45 $headers .= "Host: $hostname\r\n";
100             }
101 136   100     581 my $method = $opt_method || "GET";
102 136         313 my $body = "";
103              
104 136 100       370 if ($opt_content) {
105 74         220 $headers .= "Content-Length: " . length($opt_content) . "\r\n";
106 74         131 $body = $opt_content;
107             }
108              
109 136 100       354 if ($opt_extra_rn) {
110 36         92 $body .= "\r\n"; # some browsers on POST send an extra \r\n that's not part of content-length
111             }
112              
113 136         1621 my $send = "$method /$cmds HTTP/$self->{http_version}\r\n$headers\r\n";
114              
115 136 100       398 unless ($opt_post_header_pause) {
116 100         1027 $send .= $body;
117             }
118              
119 136         406 my $len = length $send;
120              
121             # send setup
122 136         287 my $rv;
123 136         490 my $sock = delete $self->{_sock};
124 136 50       955 local $SIG{'PIPE'} = "IGNORE" unless $FLAG_NOSIGNAL;
125              
126             ### send it cached
127 136 100       555 if ($sock) {
128 123         21210 $rv = send($sock, $send, $FLAG_NOSIGNAL);
129 123 50 33     1466 if ($! || ! defined $rv) {
    50          
130 0         0 undef $self->{_sock};
131             } elsif ($rv != $len) {
132 0         0 return undef;
133             }
134             }
135              
136             # failing that, send it through a new socket
137 136 100       533 unless ($rv) {
138 13         59 $self->{_reqdone} = 0;
139              
140 13 50       415 $sock = IO::Socket::INET->new(
141             PeerAddr => $self->{server},
142             Timeout => 3,
143             ) or return undef;
144 13 50       57748 setsockopt($sock, IPPROTO_TCP, TCP_NODELAY, pack("l", 1)) or die "failed to set sockopt: $!\n";
145              
146 13         10129 $rv = send($sock, $send, $FLAG_NOSIGNAL);
147 13 50 33     312 if ($! || $rv != $len) {
148 0         0 return undef;
149             }
150             }
151              
152 136 100       425 if ($opt_post_header_pause) {
153 36         27410881 select undef, undef, undef, $opt_post_header_pause;
154 36         217 my $len = length $body;
155 36 50       257 if ($len) {
156 36         18889 my $rv = send($sock, $body, $FLAG_NOSIGNAL);
157 36 50 33     1028 if ($! || ! defined $rv) {
    50          
158 0         0 undef $self->{_sock};
159             } elsif ($rv != $len) {
160 0         0 return undef;
161             }
162             }
163             }
164              
165             my $parse_it = sub {
166 136     136   904 my ($resp, $firstline) = resp_from_sock($sock);
167              
168 136   50     1965 my $conhdr = $resp->header("Connection") || "";
169 136 100 33     16703 if (($firstline =~ m!\bHTTP/1\.1\b! && $conhdr !~ m!\bclose\b!i) ||
      66        
      33        
170             ($firstline =~ m!\bHTTP/1\.0\b! && $conhdr =~ m!\bkeep-alive\b!i)) {
171 130         1072 $self->{_sock} = $sock;
172 130         460 $self->{_reqdone}++;
173             } else {
174 6         32 $self->{_reqdone} = 0;
175             }
176              
177 136         3393 return $resp;
178 136         2524 };
179              
180 136 50       668 if ($opt_return_reader) {
181 0         0 return $parse_it;
182             } else {
183 136         474 return $parse_it->();
184             }
185             }
186              
187             sub reqdone {
188 89     89 0 125376 my $self = shift;
189 89         616 return $self->{_reqdone};
190             }
191              
192             # general purpose URL escaping function
193             sub eurl {
194 138     138 0 341 my $a = $_[0];
195 138         430 $a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg;
  0         0  
196 138         352 $a =~ tr/ /+/;
197 138         1866 return $a;
198             }
199              
200             1;