File Coverage

blib/lib/LWPx/Record/DataSection.pm
Criterion Covered Total %
statement 68 113 60.1
branch 10 32 31.2
condition 4 14 28.5
subroutine 19 22 86.3
pod 1 5 20.0
total 102 186 54.8


line stmt bran cond sub pod time code
1             package LWPx::Record::DataSection;
2 2     2   3993 use strict;
  2         5  
  2         88  
3 2     2   11 use warnings;
  2         4  
  2         73  
4 2     2   3467 use LWP::Protocol;
  2         336274  
  2         70  
5 2     2   3244 use Data::Section::Simple;
  2         1311  
  2         126  
6 2     2   1793 use B::Hooks::EndOfScope;
  2         45190  
  2         36  
7 2     2   175 use HTTP::Response;
  2         4  
  2         50  
8 2     2   3112 use CGI::Simple;
  2         40317  
  2         18  
9 2     2   2496 use CGI::Simple::Cookie;
  2         5010  
  2         3620  
10              
11             our $VERSION = '0.01';
12              
13             our $Data;
14             our ($Pkg, $File, $Fh);
15              
16             our $Option = {
17             decode_content => 1,
18             record_response_header => undef,
19             record_request_cookie => undef,
20             record_post_param => undef,
21             append_data_section => !!$ENV{LWPX_RECORD_APPEND_DATA},
22             };
23              
24             # From HTTP::Headers
25             our @CommonHeaders = qw(
26             Cache-Control Connection Date Pragma Trailer Transfer-Encoding Upgrade
27             Via Warning
28             Accept-Ranges Age ETag Location Proxy-Authenticate Retry-After Server
29             Vary WWW-Authenticate
30             Allow Content-Encoding Content-Language Content-Length Content-Location
31             Content-MD5 Content-Range Content-Type Expires Last-Modified
32             );
33              
34             sub import {
35 2     2   18 my ($class, %args) = @_;
36              
37 2         17 while (my ($key, $value) = each %args) {
38 1         11 $key =~ s/^-//;
39 1         6 $Option->{$key} = $value;
40             }
41              
42 2         4 for (my $level = 0; ; $level++) {
43 7 50       46 my ($pkg, $file) = caller($level) or last;
44 7 100       26 next unless $file eq $0;
45              
46 2 50 33     15 if (defined $Pkg && $pkg ne $Pkg) {
47 0         0 require Carp;
48 0         0 Carp::croak("only one class can use $class");
49             }
50              
51 2         5 ($Pkg, $File) = ($pkg, $file);
52             on_scope_end {
53 2     2   86782 $class->load_data;
54              
55             # append __DATA__ section only when direct import
56 2 50 66     18 if ($level == 0 && not defined $Data) {
57 0         0 __PACKAGE__->append_to_file("\n__DATA__\n\n");
58 0         0 $Data = {};
59             }
60              
61 2         14 LWP::Protocol::Fake->fake;
62 2         18 };
63 2         113 return;
64             }
65              
66 0         0 require Carp;
67 0         0 Carp::croak "Suitable file not found: $0";
68             }
69              
70             sub load_data {
71 2     2 1 27 my $class = shift;
72 2         23 $Data = Data::Section::Simple->new($Pkg)->get_data_section;
73 2         355 return $Data;
74             }
75              
76             sub append_to_file {
77 0     0 0 0 my $class = shift;
78 0 0       0 return unless $Option->{append_data_section};
79 0 0 0     0 unless ($Fh && fileno $Fh) {
80 0 0       0 open $Fh, '>>', $File or die $!;
81             }
82 0         0 print $Fh @_;
83             }
84              
85             sub request_to_key {
86 2     2 0 5 my ($class, $req) = @_;
87              
88 2         13 my @keys = ( $req->method, $req->uri );
89 2 50       50 if (my $cookie_keys = $Option->{record_request_cookie}) {
90 0         0 my $cookie = $req->header('Cookie');
91 0         0 my %cookies = CGI::Simple::Cookie->parse($cookie);
92 0         0 push @keys, 'Cookie:' . join ',', map { "$_=" . $cookies{$_}->value } grep { $cookies{$_} } sort @$cookie_keys;
  0         0  
  0         0  
93             }
94 2 50       9 if (my $post_params = $Option->{record_post_param}) {
95 0         0 my $q = CGI::Simple->new($req->content);
96 0         0 push @keys, 'Post:' . join ',', map { my $key = $_; map { "$key=$_" } $q->param($_) } grep { $q->param($_) } sort @$post_params;
  0         0  
  0         0  
  0         0  
  0         0  
97             }
98              
99 2         14 return join ' ', @keys;
100             }
101              
102             sub restore_response {
103 2     2 0 5 my ($class, $req) = @_;
104              
105 2         9 my $key = $class->request_to_key($req);
106 2 50 33     36 if (my $string = $Data && $Data->{$key}) {
107 2         18 $string =~ s/\n\z//;
108 2 50       10 utf8::encode $string if utf8::is_utf8 $string;
109 2         14 my $res = HTTP::Response->parse($string);
110 2         1053 $res->request($req);
111 2         27 return $res;
112             }
113             }
114              
115             sub store_response {
116 0     0 0 0 my ($class, $res, $req) = @_;
117 0         0 my $key = $class->request_to_key($req);
118              
119 0         0 my $res_to_store = $res->clone;
120 0 0       0 if ($Option->{decode_content}) {
121 0         0 my $content = $res_to_store->decoded_content;
122 0 0       0 utf8::encode $content if utf8::is_utf8 $content;
123 0         0 $res_to_store->content($content);
124 0         0 $res_to_store->content_length(length $content);
125 0         0 $res_to_store->remove_header('Content-Encoding');
126             }
127              
128 0   0     0 my $record_response_header = $Option->{record_response_header} || [];
129 0 0       0 unless ($record_response_header eq ':all') {
130 0         0 my %header_to_keep = map { uc $_ => 1 } ( @CommonHeaders, @$record_response_header );
  0         0  
131 0         0 foreach ($res_to_store->header_field_names) {
132 0 0       0 $res_to_store->remove_header($_) unless $header_to_keep{ uc $_ };
133             }
134             }
135              
136 0         0 $class->append_to_file("@@ $key\n");
137 0         0 $class->append_to_file($res_to_store->as_string("\n"), "\n");
138              
139 0         0 $Data->{$key} = $res_to_store->as_string;
140             }
141              
142             package #
143             LWP::Protocol::Fake;
144              
145             our $ORIGINAL_LWP_Protocol_create = \&LWP::Protocol::create;
146              
147             sub fake {
148 2     2   6 my $class = shift;
149 2     2   25 no warnings 'redefine';
  2         5  
  2         233  
150 2     2   62 *LWP::Protocol::create = sub { LWP::Protocol::Fake->new(@_) };
  2         28395  
151             }
152              
153             sub unfake {
154 0     0   0 my $class = shift;
155 2     2   11 no warnings 'redefine';
  2         4  
  2         668  
156 0         0 *LWP::Protocol::create = $ORIGINAL_LWP_Protocol_create;
157             }
158              
159             sub new {
160 2     2   5 my ($class, $scheme, $ua) = @_;
161 2         10 bless { scheme => $scheme, ua => $ua, real => &$ORIGINAL_LWP_Protocol_create($scheme, $ua) }, $class;
162             }
163              
164             sub request {
165 2     2   108300 my ($self, $request, $proxy, $arg, $size, $timeout) = @_;
166              
167 2 50       17 if (my $res = LWPx::Record::DataSection->restore_response($request)) {
168 2         7 return $res;
169             } else {
170 0           my $res = $self->{real}->request($request, $proxy, $arg, $size, $timeout);
171 0           LWPx::Record::DataSection->store_response($res, $request);
172 0           return $res;
173             }
174             }
175              
176             1;
177              
178             __END__