The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Build.PL 15
Changes 09
MANIFEST 01
META.json 817
META.yml 2926
README.md 1016
cpanfile 17
lib/Furl/HTTP.pm 33
lib/Furl.pm 247
t/100_low/34_keep_request.t 313
t/300_high/07_cookie.t 0101
11 files changed (This is a version diff) 57245
@@ -25,12 +25,16 @@ my %args = (
 
     name            => 'Furl',
     module_name     => 'Furl',
-    allow_pure_perl => 0,
+    allow_pureperl => 0,
 
     script_files => [glob('script/*'), glob('bin/*')],
+    c_source     => [qw()],
+    PL_files => {},
 
     test_files           => ((-d '.git' || $ENV{RELEASE_TESTING}) && -d 'xt') ? 't/ xt/' : 't/',
     recursive_test_files => 1,
+
+    
 );
 if (-d 'share') {
     $args{share_dir} = 'share';
@@ -1,5 +1,14 @@
 Revision history for Perl module Furl
 
+3.02 2014-03-18T20:52:07Z
+
+    - Added new experimental cookie_jar support.
+      (tokuhirom)
+
+3.01 2014-02-13T06:19:47Z
+
+    - Fixed documentation bug(Reported by Yappo++)
+
 3.00 2013-11-13T09:39:38Z
 
     - implement inactivity_timeout (for read / write), requested by autarch++
@@ -58,6 +58,7 @@ t/300_high/02_agent.t
 t/300_high/04_http_request.t
 t/300_high/05_suppress_dup_host_header.t
 t/300_high/06_keep_request.t
+t/300_high/07_cookie.t
 t/300_high/99_error.t
 t/400_components/001_response-coding/01-file.t
 t/400_components/001_response-coding/t-euc-jp.html
@@ -4,8 +4,10 @@
       "Tokuhiro Matsuno <tokuhirom@gmail.com>"
    ],
    "dynamic_config" : 0,
-   "generated_by" : "Minilla/v0.4.2",
-   "license" : "perl_5",
+   "generated_by" : "Minilla/v0.12.0",
+   "license" : [
+      "perl_5"
+   ],
    "meta-spec" : {
       "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
       "version" : "2"
@@ -19,7 +21,8 @@
          "share",
          "eg",
          "examples",
-         "author"
+         "author",
+         "builder"
       ]
    },
    "prereqs" : {
@@ -35,18 +38,22 @@
             "Test::CPAN::Meta" : "0",
             "Test::MinimumVersion" : "0.10108",
             "Test::Pod" : "1.41",
-            "Test::Spellunker" : "v0.2.2"
+            "Test::Spellunker" : "v0.2.7"
          },
          "suggests" : {
             "Child" : "0",
             "Getopt::Long" : "0",
             "HTTP::Lite" : "0",
+            "IO::Callback" : "0",
             "LWP::UserAgent" : "0",
+            "Net::DNS::Lite" : "0",
+            "Net::IDN::Encode" : "0",
             "Plack::Loader" : "0",
             "Starman" : "0",
+            "Test::LeakTrace" : "0",
             "Test::More" : "0",
+            "Test::Requires" : "0",
             "Test::TCP" : "0",
-            "Test::suggests" : "0",
             "URI" : "0",
             "WWW::Curl::Easy" : "4.14",
             "autodie" : "0",
@@ -56,6 +63,7 @@
       "runtime" : {
          "recommends" : {
             "Compress::Raw::Zlib" : "0",
+            "HTTP::CookieJar" : "0",
             "IO::Socket::SSL" : "0",
             "Net::IDN::Encode" : "0"
          },
@@ -84,6 +92,7 @@
             "Test::TCP" : "1.06"
          },
          "suggests" : {
+            "HTTP::CookieJar" : "0",
             "HTTP::Proxy" : "0",
             "HTTP::Server::PSGI" : "0",
             "Plack" : "0",
@@ -101,14 +110,14 @@
    "provides" : {
       "Furl" : {
          "file" : "lib/Furl.pm",
-         "version" : "3.00"
+         "version" : "3.02"
       },
       "Furl::ConnectionCache" : {
          "file" : "lib/Furl/ConnectionCache.pm"
       },
       "Furl::HTTP" : {
          "file" : "lib/Furl/HTTP.pm",
-         "version" : "3.00"
+         "version" : "3.02"
       },
       "Furl::Headers" : {
          "file" : "lib/Furl/Headers.pm"
@@ -134,7 +143,7 @@
          "web" : "https://github.com/tokuhirom/Furl"
       }
    },
-   "version" : "3.00",
+   "version" : "3.02",
    "x_contributors" : [
       "Keiji, Yoshimi <walf443@gmail.com>",
       "Fuji, Goro <gfuji@cpan.org>",
@@ -3,20 +3,20 @@ abstract: 'Lightning-fast URL fetcher'
 author:
   - 'Tokuhiro Matsuno <tokuhirom@gmail.com>'
 build_requires:
-  File::Temp: 0
-  Test::More: 0.96
-  Test::Requires: 0
-  Test::TCP: 1.06
+  File::Temp: '0'
+  Test::More: '0.96'
+  Test::Requires: '0'
+  Test::TCP: '1.06'
 configure_requires:
-  CPAN::Meta: 0
-  CPAN::Meta::Prereqs: 0
-  Module::Build: 0.38
+  CPAN::Meta: '0'
+  CPAN::Meta::Prereqs: '0'
+  Module::Build: '0.38'
 dynamic_config: 0
-generated_by: 'Minilla/v0.4.2, CPAN::Meta::Converter version 2.120921'
+generated_by: 'Minilla/v0.12.0, CPAN::Meta::Converter version 2.133380'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
-  version: 1.4
+  version: '1.4'
 name: Furl
 no_index:
   directory:
@@ -27,47 +27,44 @@ no_index:
     - eg
     - examples
     - author
+    - builder
 provides:
   Furl:
     file: lib/Furl.pm
-    version: 3.00
+    version: '3.02'
   Furl::ConnectionCache:
     file: lib/Furl/ConnectionCache.pm
-    version: 0
   Furl::HTTP:
     file: lib/Furl/HTTP.pm
-    version: 3.00
+    version: '3.02'
   Furl::Headers:
     file: lib/Furl/Headers.pm
-    version: 0
   Furl::Request:
     file: lib/Furl/Request.pm
-    version: 0
   Furl::Response:
     file: lib/Furl/Response.pm
-    version: 0
   Furl::ZlibStream:
     file: lib/Furl/ZlibStream.pm
-    version: 0
 recommends:
-  Compress::Raw::Zlib: 0
-  IO::Socket::SSL: 0
-  Net::IDN::Encode: 0
+  Compress::Raw::Zlib: '0'
+  HTTP::CookieJar: '0'
+  IO::Socket::SSL: '0'
+  Net::IDN::Encode: '0'
 requires:
-  Class::Accessor::Lite: 0
-  Encode: 0
-  HTTP::Parser::XS: 0.11
-  MIME::Base64: 0
-  Mozilla::CA: 0
-  Scalar::Util: 0
-  Socket: 0
-  Time::HiRes: 0
-  perl: 5.008001
+  Class::Accessor::Lite: '0'
+  Encode: '0'
+  HTTP::Parser::XS: '0.11'
+  MIME::Base64: '0'
+  Mozilla::CA: '0'
+  Scalar::Util: '0'
+  Socket: '0'
+  Time::HiRes: '0'
+  perl: '5.008001'
 resources:
   bugtracker: https://github.com/tokuhirom/Furl/issues
   homepage: https://github.com/tokuhirom/Furl
   repository: git://github.com/tokuhirom/Furl.git
-version: 3.00
+version: '3.02'
 x_contributors:
   - 'Keiji, Yoshimi <walf443@gmail.com>'
   - 'Fuji, Goro <gfuji@cpan.org>'
@@ -48,18 +48,23 @@ _%args_ might be:
 - max\_redirects :Int = 7
 - capture\_request :Bool = false
 
-    If this parameter is true, [Furl::HTTP](http://search.cpan.org/perldoc?Furl::HTTP) captures raw request string.
+    If this parameter is true, [Furl::HTTP](https://metacpan.org/pod/Furl::HTTP) captures raw request string.
     You can get it by `$res->captured_req_headers` and `$res->captured_req_content`.
 
 - proxy :Str
 - no\_proxy :Str
 - headers :ArrayRef
+- cookie\_jar :Object
+
+    (EXPERIMENTAL)
+
+    An instance of HTTP::CookieJar or equivalent class that supports the add and cookie\_header methods
 
 ## Instance Methods
 
 ### `$furl->request([$request,] %args) :Furl::Response`
 
-Sends an HTTP request to a specified URL and returns a instance of [Furl::Response](http://search.cpan.org/perldoc?Furl::Response).
+Sends an HTTP request to a specified URL and returns a instance of [Furl::Response](https://metacpan.org/pod/Furl::Response).
 
 _%args_ might be:
 
@@ -154,12 +159,12 @@ Loads proxy settings from `$ENV{HTTP_PROXY}` and `$ENV{NO_PROXY}`.
 
 - I need more speed.
 
-    See [Furl::HTTP](http://search.cpan.org/perldoc?Furl::HTTP), which provides the low level interface of [Furl](http://search.cpan.org/perldoc?Furl).
-    It is faster than `Furl.pm` since [Furl::HTTP](http://search.cpan.org/perldoc?Furl::HTTP) does not create response objects.
+    See [Furl::HTTP](https://metacpan.org/pod/Furl::HTTP), which provides the low level interface of [Furl](https://metacpan.org/pod/Furl).
+    It is faster than `Furl.pm` since [Furl::HTTP](https://metacpan.org/pod/Furl::HTTP) does not create response objects.
 
 - How do you use cookie\_jar?
 
-    Furl does not directly support the cookie\_jar option available in LWP. You can use [HTTP::Cookies](http://search.cpan.org/perldoc?HTTP::Cookies), [HTTP::Request](http://search.cpan.org/perldoc?HTTP::Request), [HTTP::Response](http://search.cpan.org/perldoc?HTTP::Response) like following.
+    Furl does not directly support the cookie\_jar option available in LWP. You can use [HTTP::Cookies](https://metacpan.org/pod/HTTP::Cookies), [HTTP::Request](https://metacpan.org/pod/HTTP::Request), [HTTP::Response](https://metacpan.org/pod/HTTP::Response) like following.
 
         my $f = Furl->new();
         my $cookies = HTTP::Cookies->new();
@@ -224,6 +229,7 @@ Loads proxy settings from `$ENV{HTTP_PROXY}` and `$ENV{NO_PROXY}`.
                 if $received_size >= $next_update;
             }
         );
+
 - HTTPS requests claims warnings!
 
     When you make https requests, IO::Socket::SSL may complain about it like:
@@ -247,7 +253,7 @@ Loads proxy settings from `$ENV{HTTP_PROXY}` and `$ENV{NO_PROXY}`.
             },
         });
 
-    See [IO::Socket::SSL](http://search.cpan.org/perldoc?IO::Socket::SSL) for details.
+    See [IO::Socket::SSL](https://metacpan.org/pod/IO::Socket::SSL) for details.
 
 # AUTHOR
 
@@ -273,13 +279,13 @@ audreyt
 
 # SEE ALSO
 
-[LWP](http://search.cpan.org/perldoc?LWP)
+[LWP](https://metacpan.org/pod/LWP)
 
-[IO::Socket::SSL](http://search.cpan.org/perldoc?IO::Socket::SSL)
+[IO::Socket::SSL](https://metacpan.org/pod/IO::Socket::SSL)
 
-[Furl::HTTP](http://search.cpan.org/perldoc?Furl::HTTP)
+[Furl::HTTP](https://metacpan.org/pod/Furl::HTTP)
 
-[Furl::Response](http://search.cpan.org/perldoc?Furl::Response)
+[Furl::Response](https://metacpan.org/pod/Furl::Response)
 
 # LICENSE
 
@@ -16,6 +16,7 @@ suggests 'HTTP::Response'; # Furl::Response
 recommends 'Net::IDN::Encode';    # for International Domain Name
 recommends 'IO::Socket::SSL';     # for SSL
 recommends 'Compress::Raw::Zlib'; # for Content-Encoding
+recommends 'HTTP::CookieJar';
 
 on test => sub {
     requires 'Test::More' => 0.96;    # done_testing, subtest
@@ -33,6 +34,7 @@ on test => sub {
     suggests 'parent';
     suggests 'Plack';
     suggests 'Test::Valgrind';
+    suggests 'HTTP::CookieJar';
 };
 
 on develop => sub {
@@ -43,11 +45,15 @@ on develop => sub {
     suggests 'Plack::Loader';
     suggests 'Starman';
     suggests 'Test::More';
-    suggests 'Test::suggests';
+    suggests 'Test::Requires';
     suggests 'Test::TCP';
     suggests 'URI';
     suggests 'WWW::Curl::Easy', '4.14';
+    suggests 'IO::Callback';
     suggests 'autodie';
     suggests 'parent';
+    suggests 'Net::IDN::Encode';
+    suggests 'Test::LeakTrace';
+    suggests 'Net::DNS::Lite';
 };
 
@@ -4,7 +4,7 @@ use warnings;
 use base qw/Exporter/;
 use 5.008001;
 
-our $VERSION = '3.00';
+our $VERSION = '3.02';
 
 use Carp ();
 use Furl::ConnectionCache;
@@ -564,7 +564,7 @@ sub request {
 
     return (
         $res_minor_version, $res_status, $res_msg, $res_headers, $res_content,
-        $req_headers, $req_content,
+        $req_headers, $req_content, undef, undef, [$scheme, $username, $password, $host, $port, $path_query],
     );
 }
 
@@ -1297,7 +1297,7 @@ The example below sends all requests to 127.0.0.1:8080.
     my $ua = Furl::HTTP->new(
         get_address => sub {
             my ($host, $port, $timeout) = @_;
-            sockaddr_in(8080, inet_aton("127.0.0.1"));
+            pack_sockaddr_in(8080, inet_aton("127.0.0.1"));
         },
     );
 
@@ -6,7 +6,7 @@ use Furl::HTTP;
 use Furl::Request;
 use Furl::Response;
 use Carp ();
-our $VERSION = '3.00';
+our $VERSION = '3.02';
 
 use 5.008001;
 
@@ -100,6 +100,25 @@ sub request {
         $args{headers} = $headers;
     }
 
+    my $cookie_jar = ${$self}->{cookie_jar};
+
+    if ($cookie_jar) {
+        my $url;
+        if ($args{url}) {
+            $url = $args{url};
+        } else {
+            $url = join(
+                '',
+                $args{scheme},
+                '://',
+                $args{host},
+                (exists($args{port}) ? ":$args{port}" : ()),
+                exists($args{path_query}) ? $args{path_query} : '/',
+            );
+        }
+        push @{$args{headers}}, 'Cookie' => $cookie_jar->cookie_header($url);
+    }
+
     my (
         $res_minor_version,
         $res_status,
@@ -107,10 +126,30 @@ sub request {
         $res_headers,
         $res_content,
         $captured_req_headers,
-        $captured_req_content ) = ${$self}->request(%args);
+        $captured_req_content,
+        $captured_res_headers,
+        $captured_res_content,
+        $request_info,
+        ) = ${$self}->request(%args);
 
     my $res = Furl::Response->new($res_minor_version, $res_status, $res_msg, $res_headers, $res_content);
     $res->set_request_info(\%args, $captured_req_headers, $captured_req_content);
+
+    if ($cookie_jar) {
+        my ($scheme, $username, $password, $host, $port, $path_query) = @$request_info;
+        my $req_url = join(
+            '',
+            $scheme,
+            '://',
+            (defined($username) && defined($password) ? "${username}:${password}@" : ()),
+            "$host:${port}${path_query}",
+        );
+        for my $cookie ($res->header('Set-Cookie')) {
+            # Do not use $args{url} as a url. Because the server may redirected.
+            $cookie_jar->add($req_url, $cookie);
+        }
+    }
+
     return $res;
 }
 
@@ -183,6 +222,12 @@ You can get it by C<< $res->captured_req_headers >> and C<< $res->captured_req_c
 
 =item headers :ArrayRef
 
+=item cookie_jar :Object
+
+(EXPERIMENTAL)
+
+An instance of HTTP::CookieJar or equivalent class that supports the add and cookie_header methods
+
 =back
 
 =head2 Instance Methods
@@ -14,9 +14,19 @@ test_tcp(
             my $furl = Furl::HTTP->new(capture_request => 1);
             my @res = $furl->request( url => "http://127.0.0.1:$port/1", );
 
-            my $content = pop @res;
-            my $headers = pop @res;
-            my $req = Furl::Request->parse($headers . $content);
+            my (
+                $res_minor_version,
+                $res_status,
+                $res_msg,
+                $res_headers,
+                $res_content,
+                $captured_req_headers,
+                $captured_req_content,
+                $captured_res_headers,
+                $captured_res_content,
+                $request_info,
+            ) = @res;
+            my $req = Furl::Request->parse($captured_req_headers . $captured_req_content);
 
             is $req->method, 'GET';
             is $req->uri, "http://127.0.0.1:$port/1";
@@ -0,0 +1,101 @@
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use Test::Requires 'HTTP::CookieJar', 'Plack::Request', 'Plack::Loader', 'Plack::Builder', 'Plack::Response';
+use Test::TCP;
+use Furl;
+
+subtest 'Simple case', sub {
+    test_tcp(
+        client => sub {
+            my $port = shift;
+            my $furl = Furl->new(
+                cookie_jar => HTTP::CookieJar->new()
+            );
+            my $url = "http://127.0.0.1:$port";
+
+            subtest 'first time access', sub {
+                my $res = $furl->get("${url}/");
+
+                note "Then, response should be 200 OK";
+                is $res->status, 200;
+                note "And, content should be 'OK 1'";
+                is $res->content, 'OK 1';
+            };
+
+            subtest 'Second time access', sub {
+                my $res = $furl->get("${url}/");
+
+                note "Then, response should be 200 OK";
+                is $res->status, 200;
+                note "And, content should be 'OK 2'";
+                is $res->content, 'OK 2';
+            };
+        },
+        server => \&session_server,
+    );
+};
+
+subtest '->request(host => ...) style simple interface', sub {
+    test_tcp(
+        client => sub {
+            my $port = shift;
+            my $furl = Furl->new(
+                cookie_jar => HTTP::CookieJar->new()
+            );
+
+            subtest 'first time access', sub {
+                my $res = $furl->request(
+                    method => 'GET',
+                    scheme => 'http',
+                    host => '127.0.0.1',
+                    port => $port,
+                );
+
+                note "Then, response should be 200 OK";
+                is $res->status, 200;
+                note "And, content should be 'OK 1'";
+                is $res->content, 'OK 1';
+            };
+
+            subtest 'Second time access', sub {
+                my $res = $furl->request(
+                    method => 'GET',
+                    scheme => 'http',
+                    host => '127.0.0.1',
+                    port => $port,
+                );
+
+                note "Then, response should be 200 OK";
+                is $res->status, 200;
+                note "And, content should be 'OK 2'";
+                is $res->content, 'OK 2';
+            };
+        },
+        server => \&session_server,
+    );
+};
+
+done_testing;
+
+sub session_server {
+    my $port = shift;
+    my %SESSION_STORE;
+    Plack::Loader->auto( port => $port )->run(builder {
+        enable 'ContentLength';
+
+        sub {
+            my $env     = shift;
+            my $req = Plack::Request->new($env);
+            my $session_key = $req->cookies->{session_key} || rand();
+            my $cnt = ++$SESSION_STORE{$session_key};
+            note "CNT: $cnt";
+            my $res = Plack::Response->new(
+                200, [], ["OK ${cnt}"]
+            );
+            $res->cookies->{'session_key'} = $session_key;
+            return $res->finalize;
+        };
+    });
+}