1 package HTTP
::AnyUA
::Util
;
2 # ABSTRACT: Utility subroutines for HTTP::AnyUA backends
7 our $VERSION = '9999.999'; # VERSION
9 use Exporter
qw(import);
13 http_headers_to_native
14 native_to_http_request
15 coderef_content_to_string
24 sub _croak
{ require Carp
; Carp
::croak
(@_) }
25 sub _usage
{ _croak
("Usage: @_\n") }
27 =func coderef_content_to_string
29 $content = coderef_content_to_string
(\
&code
);
30 $content = coderef_content_to_string
($content); # noop
32 Convert a coderef into a string of content by iteratively calling the coderef
and concatenating the
33 chunks it provides
until the coderef returns
undef or an empty string
.
37 sub coderef_content_to_string
{
40 return $content if !$content;
42 if (ref($content) eq 'CODE') {
43 # drain the request body
45 while (my $chunk = $content->()) {
54 =func native_to_http_request
56 $http_request = native_to_http_request
($method, $url);
57 $http_request = native_to_http_request
($method, $url, \
%options);
59 Convert a
"native" request tuple to an L
<HTTP
::Request
> object
.
63 sub native_to_http_request
{
66 my $args = shift || {};
69 my $content = $args->{content
}; # works as either scalar or coderef
72 for my $header (keys %{$args->{headers
} || {}}) {
73 my $value = $args->{headers
}{$header};
74 my @values = ref($value) eq 'ARRAY' ? @$value : ($value);
76 push @$headers, ($header => $v);
80 require HTTP
::Request
;
81 return HTTP
::Request-
>new($method, $url, $headers, $content);
84 =func http_headers_to_native
86 $headers = http_headers_to_native
($http_headers);
88 Convert an L
<HTTP
::Headers
> object to a
"native" hashref
.
92 sub http_headers_to_native
{
93 my $http_headers = shift;
97 for my $header ($http_headers->header_field_names) {
98 my @values = $http_headers->header($header);
99 $native->{lc($header)} = @values == 1 ? $values[0] : [@values];
105 =func internal_exception
107 $response = internal_exception
($content);
108 $response = internal_exception
($content, $response);
110 Create an internal exception response
. If an existing response
is passed
, that response will have
111 its fields modified to become an internal exception
.
115 sub internal_exception
{
116 my $e = shift or _usage
(q{internal_exception($exception)});
117 my $resp = shift || {};
121 $resp->{headers
}{'client-original-status'} = $resp->{status
} if $resp->{status
};
122 $resp->{headers
}{'client-original-reason'} = $resp->{reason
} if $resp->{reason
};
124 $resp->{success
} = '';
125 $resp->{status
} = 599;
126 $resp->{reason
} = 'Internal Exception';
127 $resp->{content
} = $e;
128 $resp->{headers
}{'content-type'} = 'text/plain';
129 $resp->{headers
}{'content-length'} = length $e;
136 ($scheme, $host, $port, $path_query, $auth) = split_url
($url);
138 Split a URL into its components
.
142 # adapted from HTTP/Tiny.pm
144 my $url = shift or _usage
(q{split_url($url)});
146 # URI regex adapted from the URI module
147 my ($scheme, $host, $path_query) = $url =~ m
<\A
([^:/?#]+)://([^/?#]*)([^#]*)>
148 or die(qq
/Cannot parse URL: '$url'\n/);
150 $scheme = lc $scheme;
151 $path_query = "/$path_query" unless $path_query =~ m
<\A
/>;
154 if ( (my $i = index $host, '@') != -1 ) {
156 $auth = substr $host, 0, $i, ''; # take up to the @ for auth
157 substr $host, 0, 1, ''; # knock the @ off the host
159 # userinfo might be percent escaped, so recover real auth info
160 $auth =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
162 my $port = $host =~ s/:(\d*)\z// && length $1 ? $1
163 : $scheme eq 'http' ? 80
164 : $scheme eq 'https' ? 443
167 return ($scheme, (length $host ? lc $host : "localhost") , $port, $path_query, $auth);
172 $http_date = http_date
($epoch_time);
174 Convert an epoch
time into a date format suitable
for HTTP
.
178 # Date conversions adapted from HTTP::Date
179 # adapted from HTTP/Tiny.pm
180 my $DoW = 'Sun|Mon|Tue|Wed|Thu|Fri|Sat';
181 my $MoY = 'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec';
183 my $time = shift or _usage
(q{http_date($time)});
184 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($time);
185 return sprintf('%s, %02d %s %04d %02d:%02d:%02d GMT',
186 substr($DoW,$wday*4,3),
187 $mday, substr($MoY,$mon*4,3), $year+1900,
192 =func parse_http_date
194 $epoch_time = parse_http_date
($http_date);
196 Convert an HTTP date into an epoch
time. Returns
undef if the date cannot be parsed
.
200 # adapted from HTTP/Tiny.pm
201 sub parse_http_date
{
202 my $str = shift or _usage
(q{parse_http_date($str)});
204 if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) {
205 @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3);
207 elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) {
208 @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3);
210 elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) {
211 @tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6);
215 my $t = @tl_parts ? Time
::Local
::timegm
(@tl_parts) : -1;
222 $escaped = uri_escape
($unescaped);
224 Escape a string
for use in a URL query param
or as C
<application
/x-www-form-urlencoded
> data
.
228 # URI escaping adapted from URI::Escape
229 # c.f. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
230 # perl 5.6 ready UTF-8 encoding adapted from JSON::PP
231 # adapted from HTTP/Tiny.pm
232 my %escapes = map { chr($_) => sprintf('%%%02X', $_) } 0..255;
234 my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/;
237 my $str = shift or _usage
(q{uri_escape($str)});
242 $str = pack('U*', unpack('C*', $str)) # UTF-8 encode a byte string
243 if (length $str == do { use bytes
; length $str });
244 $str = pack('C*', unpack('C*', $str)); # clear UTF-8 flag
246 $str =~ s/($unsafe_char)/$escapes{$1}/ge;
250 =func www_form_urlencode
252 $bytes = www_form_urlencode
(\
%form_data);
253 $bytes = www_form_urlencode
(\
@form_data);
255 Encode a hashref
or arrayref as C
<application
/x-www-form-urlencoded
> data
.
259 # adapted from HTTP/Tiny.pm
260 sub www_form_urlencode
{
263 or _usage
(q{www_form_urlencode($dataref)});
264 (ref $data eq 'HASH' || ref $data eq 'ARRAY')
265 or _croak
("form data must be a hash or array reference\n");
267 my @params = ref $data eq 'HASH' ? %$data : @$data;
269 or _croak
("form data reference must have an even number of terms\n");
273 my ($key, $value) = splice(@params, 0, 2);
274 if (ref $value eq 'ARRAY') {
275 unshift @params, map { $key => $_ } @$value;
278 push @terms, join('=', map { uri_escape
($_) } $key, $value);
282 return join('&', ref($data) eq 'ARRAY' ? @terms : sort @terms);