File Coverage

inc/LWPx/Record/DataSection.pm
Criterion Covered Total %
statement 72 119 60.5
branch 9 32 28.1
condition 3 14 21.4
subroutine 21 24 87.5
pod 1 5 20.0
total 106 194 54.6


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