# ====================================================================
# Gossamer Threads Module Library - http://gossamer-threads.com/
#
#   GT::WWW::http::Response
#   Author: Jason Rhinelander
#   CVS Info :                          
#   $Id: Response.pm,v 1.8 2004/08/04 19:23:07 jagerman Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
# ====================================================================
#
# Description:
#   Response object for GT::WWW HTTP/HTTPS requests.
#

package GT::WWW::http::Response;
use strict;

use vars qw/$AUTOLOAD/;
use overload
    '""' => \&content,
    bool => \&boolean,
    cmp => \&strcmp;
use Carp;

sub new {
    my $class = shift;
    $class = ref $class if ref $class;

    my $self = {};
    bless $self, $class;
}

AUTOLOAD {
    my ($self, @args) = @_;
    my ($attr) = $AUTOLOAD =~ /([^:]+)$/;
    if (@args) {
        $self->{$attr} = shift @args;
    }
    $self->{$attr};
}

sub content { $_[0]->{content} }

sub boolean { 1 } # So you can you do things like: $www->get() or die

sub status {
    my $self = shift;
    if (@_) {
        my ($num, $str) = @_;
        $self->{status} = GT::WWW::http::Response::Status->new($num, $str);
    }
    $self->{status};
}

sub header {
    my $self = shift;
    if (@_) {
        $self->{header}->header(@_);
    }
    else {
        $self->{header};
    }
}

sub strcmp { $_[2] ? $_[1] cmp $_[0]->{content} : $_[0]->{content} cmp $_[1] }

package GT::WWW::http::Response::Status;

use overload
    '""' => \&string,
    bool => \&boolean,
    '0+' => \&numeric,
    '+'  => \&addition,
    '<=>' => \&numcmp,
    'cmp' => \&strcmp;

sub new {
    my ($class, $numeric, $string) = @_;
    my $self = [$numeric, $string];
    bless $self, $class;
}

sub numeric  { $_[0]->[0] }
sub string   { "$_[0]->[0] $_[0]->[1]" }
sub boolean  { substr($_[0]->[0], 0, 1) eq '2' }
sub addition { int($_[0]) + int($_[1]) }
sub numcmp   { $_[2] ? $_[1] <=> $_[0]->[0] : $_[0]->[0] <=> $_[1] }
sub strcmp   { $_[2] ? $_[1] cmp $_[0]->[1] : $_[0]->[1] cmp $_[1] }

1;

__END__

=head1 NAME

GT::WWW::http::Response and GT::WWW::http::Response::Status - Overloaded
response objects for HTTP request data.

=head1 SYNOPSIS

    # ($www is continued from GT::WWW::http SYNOPSIS)

    my $response = $www->get(); # or post(), or head()
    # -- or, after having called get(), post() or head(): --
    my $response = $www->response();

    my $status   = $response->status();

    my $content = "$response";
    my $response_code = int($status); # i.e. 200, 404, 500
    my $response_str = "$status"; # i.e. 'OK', 'Not Found', 'Internal Server Error'
    if ($status) { # True for 2xx requests, false otherwise (e.g. 404, 500, etc.)
        ...
    }

=head1 DESCRIPTION

GT::WWW::http::Response objects are returned by the L<C<get()>|GT::WWW/get>,
L<C<post()>|GT::WWW/post>, and L<C<head()>|GT::WWW/head> methods of GT::WWW
HTTP requests (and derivatives - i.e. HTTPS), or by calling
L<C<response()>|GT::WWW::http/response> after having made such a request.  The
objects are overloaded in order to provide a simple interface to the response,
while still having all the information available.

A response object always returns true in boolean context, allowing you to do
things like C<$www-E<gt>get($url) or die;> - even when a page is empty, or
contains just '0'.

=head1 CONTENT

In addition to the methods described below, the way to simply access the data
returned by the server is to simply use it like a string - for example,
printing it, concatenating it with another string, or quoting it.

You should, however, take note that when using the L<C<chunk()>|GT::WWW/chunk>
option for an HTTP request, the content will not be available.

=head1 METHODS

For simple requests, often the content alone is enough.  The following methods
are used to determine any other information available about the response.

=head2 content

Returns the content of the HTTP response.  Note that this returns the exact
same value as using the object in double quotes.

=head2 status

Returns the response status object for the request.  This object provides three
pieces of information, and has no public methods.  Instead, the data is
retrieved based on the context of the object.

    my $status = $response->status;

(N.B. Though the examples below use a C<$status> variable, there is no reason
they couldn't be written to use C<$response-E<gt>status> instead.)

=over 4

=item numeric status

The numeric status of an HTTP request (e.g. 200, 404, 500) is available simply
by using the status object as a number.

    my $numeric_status = int $status;

=item string status

The string status of an HTTP request (e.g. "OK", "Not Found", "Internal Server
Error") is available by using the status object as a string (e.g. printing it,
or concatenating it with another string).

    # Assign the status string to a variable:
    my $status_string = "$status";

    # Print out the status string:
    print $status;

    # To get a string such as "500 Internal Server Error":
    my $string = int($status) . " " . $status;

=item boolean status

In order to quickly determine whether or not a request was successful, you can
use the status object in a boolean context.

Success is determined by the numeric status of the response.  Any 2xx status
(usually 200 OK, but there are others) counts as a successful response, while
any other status counts as a failure.

    if ($status) { print "Request successful!" }
    else         { print "Request failed!"     }

=back

=head2 header

This method, called without arguments, returns the
L<header|GT::WWW::http::Header> object for the response.

    my $header = $response->header;

If this method is called with arguments, those arguments are passed to the
L<C<header()>|GT::WWW::http::Header/header> method of the header object.  This
allows this useful shortcut:

    my $some_header_value = $response->header("Some-Header");

instead of the alternative (which also works):

    my $some_header_value = $response->header->header("Some-Header");

Information on header object usage is contained in L<GT::WWW::http::Header>.

Note that although a header object allows for header manipulation, changing the
headers of a response object should be considered bad practise, and is strongly
discouraged.

=head1 CAVEATS

Although the response object _works_ like a string, keep in mind that it is
still an object, and thus a reference.  If you intend to pass the data to
another subroutine expecting a string, it is recommended that you force the
content into string form, either by quoting the variable (C<"$var">) or by
calling the content() method (C<$var-E<gt>content>).  Not doing so can lead to
unexpected results, particularly in cases where another subroutine may
differentiate between a string and a reference, and not just use the value as a
string.

Also, in terms of speed, obtaining the content (not the object) into another
variable (either via C<"$var"> or C<$var-E<gt>content>) can make quite a
substantial difference when several string comparison operations are performed.
The reason is simply that every time the object is used is a string, the
content method is called, which can amount to a significant slowdown.

Although string operations that change the string (i.e. s///) appear to work,
they in fact clobber the reference and turn your variable into an ordinary
string.  This should not be done - if the string needs to be modified, take a
copy of it first, and modify the copy.

=head1 SEE ALSO

L<GT::WWW>
L<GT::WWW::http>
L<GT::WWW::http::Header>
RFC 2616: L<http://www.ietf.org/rfc/rfc2616.txt>

=head1 MAINTAINER

Jason Rhinelander

=head1 COPYRIGHT

Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
http://www.gossamer-threads.com/

=head1 VERSION

Revision: $Id: Response.pm,v 1.8 2004/08/04 19:23:07 jagerman Exp $

=cut
