aboutsummaryrefslogtreecommitdiff
path: root/samples/Perl/Response.pm
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2012-07-23 15:52:49 -0500
committerJoshua Peek <josh@joshpeek.com>2012-07-23 15:52:49 -0500
commit7b6caa0f6c7603aabb5e6ed657e24aaddbeafc78 (patch)
treeec0522eae75bb04ae73d28ae3f599682a2bfdaeb /samples/Perl/Response.pm
parent314f0e485227915931db361be14d308a210bedea (diff)
Rename samples subdirectories
Diffstat (limited to 'samples/Perl/Response.pm')
-rw-r--r--samples/Perl/Response.pm309
1 files changed, 309 insertions, 0 deletions
diff --git a/samples/Perl/Response.pm b/samples/Perl/Response.pm
new file mode 100644
index 0000000..c1b8a7d
--- /dev/null
+++ b/samples/Perl/Response.pm
@@ -0,0 +1,309 @@
+package Plack::Response;
+use strict;
+use warnings;
+our $VERSION = '0.9988';
+$VERSION = eval $VERSION;
+
+use Plack::Util::Accessor qw(body status);
+use Carp ();
+use Scalar::Util ();
+use HTTP::Headers;
+use URI::Escape ();
+
+sub code { shift->status(@_) }
+sub content { shift->body(@_) }
+
+sub new {
+ my($class, $rc, $headers, $content) = @_;
+
+ my $self = bless {}, $class;
+ $self->status($rc) if defined $rc;
+ $self->headers($headers) if defined $headers;
+ $self->body($content) if defined $content;
+
+ $self;
+}
+
+sub headers {
+ my $self = shift;
+
+ if (@_) {
+ my $headers = shift;
+ if (ref $headers eq 'ARRAY') {
+ Carp::carp("Odd number of headers") if @$headers % 2 != 0;
+ $headers = HTTP::Headers->new(@$headers);
+ } elsif (ref $headers eq 'HASH') {
+ $headers = HTTP::Headers->new(%$headers);
+ }
+ return $self->{headers} = $headers;
+ } else {
+ return $self->{headers} ||= HTTP::Headers->new();
+ }
+}
+
+sub cookies {
+ my $self = shift;
+ if (@_) {
+ $self->{cookies} = shift;
+ } else {
+ return $self->{cookies} ||= +{ };
+ }
+}
+
+sub header { shift->headers->header(@_) } # shortcut
+
+sub content_length {
+ shift->headers->content_length(@_);
+}
+
+sub content_type {
+ shift->headers->content_type(@_);
+}
+
+sub content_encoding {
+ shift->headers->content_encoding(@_);
+}
+
+sub location {
+ my $self = shift;
+ return $self->headers->header('Location' => @_);
+}
+
+sub redirect {
+ my $self = shift;
+
+ if (@_) {
+ my $url = shift;
+ my $status = shift || 302;
+ $self->location($url);
+ $self->status($status);
+ }
+
+ return $self->location;
+}
+
+sub finalize {
+ my $self = shift;
+ Carp::croak "missing status" unless $self->status();
+
+ my $headers = $self->headers->clone;
+ $self->_finalize_cookies($headers);
+
+ return [
+ $self->status,
+ +[
+ map {
+ my $k = $_;
+ map {
+ my $v = $_;
+ $v =~ s/\015\012[\040|\011]+/chr(32)/ge; # replace LWS with a single SP
+ $v =~ s/\015|\012//g; # remove CR and LF since the char is invalid here
+
+ ( $k => $v )
+ } $headers->header($_);
+
+ } $headers->header_field_names
+ ],
+ $self->_body,
+ ];
+}
+
+sub _body {
+ my $self = shift;
+ my $body = $self->body;
+ $body = [] unless defined $body;
+ if (!ref $body or Scalar::Util::blessed($body) && overload::Method($body, q("")) && !$body->can('getline')) {
+ return [ $body ];
+ } else {
+ return $body;
+ }
+}
+
+sub _finalize_cookies {
+ my($self, $headers) = @_;
+
+ while (my($name, $val) = each %{$self->cookies}) {
+ my $cookie = $self->_bake_cookie($name, $val);
+ $headers->push_header('Set-Cookie' => $cookie);
+ }
+}
+
+sub _bake_cookie {
+ my($self, $name, $val) = @_;
+
+ return '' unless defined $val;
+ $val = { value => $val } unless ref $val eq 'HASH';
+
+ my @cookie = ( URI::Escape::uri_escape($name) . "=" . URI::Escape::uri_escape($val->{value}) );
+ push @cookie, "domain=" . $val->{domain} if $val->{domain};
+ push @cookie, "path=" . $val->{path} if $val->{path};
+ push @cookie, "expires=" . $self->_date($val->{expires}) if $val->{expires};
+ push @cookie, "secure" if $val->{secure};
+ push @cookie, "HttpOnly" if $val->{httponly};
+
+ return join "; ", @cookie;
+}
+
+my @MON = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
+my @WDAY = qw( Sun Mon Tue Wed Thu Fri Sat );
+
+sub _date {
+ my($self, $expires) = @_;
+
+ if ($expires =~ /^\d+$/) {
+ # all numbers -> epoch date
+ # (cookies use '-' as date separator, HTTP uses ' ')
+ my($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($expires);
+ $year += 1900;
+
+ return sprintf("%s, %02d-%s-%04d %02d:%02d:%02d GMT",
+ $WDAY[$wday], $mday, $MON[$mon], $year, $hour, $min, $sec);
+
+ }
+
+ return $expires;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Plack::Response - Portable HTTP Response object for PSGI response
+
+=head1 SYNOPSIS
+
+ use Plack::Response;
+
+ sub psgi_handler {
+ my $env = shift;
+
+ my $res = Plack::Response->new(200);
+ $res->content_type('text/html');
+ $res->body("Hello World");
+
+ return $res->finalize;
+ }
+
+=head1 DESCRIPTION
+
+Plack::Response allows you a way to create PSGI response array ref through a simple API.
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+ $res = Plack::Response->new;
+ $res = Plack::Response->new($status);
+ $res = Plack::Response->new($status, $headers);
+ $res = Plack::Response->new($status, $headers, $body);
+
+Creates a new Plack::Response object.
+
+=item status
+
+ $res->status(200);
+ $status = $res->status;
+
+Sets and gets HTTP status code. C<code> is an alias.
+
+=item headers
+
+ $headers = $res->headers;
+ $res->headers([ 'Content-Type' => 'text/html' ]);
+ $res->headers({ 'Content-Type' => 'text/html' });
+ $res->headers( HTTP::Headers->new );
+
+Sets and gets HTTP headers of the response. Setter can take either an
+array ref, a hash ref or L<HTTP::Headers> object containing a list of
+headers.
+
+=item body
+
+ $res->body($body_str);
+ $res->body([ "Hello", "World" ]);
+ $res->body($io);
+
+Gets and sets HTTP response body. Setter can take either a string, an
+array ref, or an IO::Handle-like object. C<content> is an alias.
+
+Note that this method doesn't automatically set I<Content-Length> for
+the response. You have to set it manually if you want, with the
+C<content_length> method (see below).
+
+=item header
+
+ $res->header('X-Foo' => 'bar');
+ my $val = $res->header('X-Foo');
+
+Shortcut for C<< $res->headers->header >>.
+
+=item content_type, content_length, content_encoding
+
+ $res->content_type('text/plain');
+ $res->content_length(123);
+ $res->content_encoding('gzip');
+
+Shortcut for the equivalent get/set methods in C<< $res->headers >>.
+
+=item redirect
+
+ $res->redirect($url);
+ $res->redirect($url, 301);
+
+Sets redirect URL with an optional status code, which defaults to 302.
+
+Note that this method doesn't normalize the given URI string. Users of
+this module have to be responsible about properly encoding URI paths
+and parameters.
+
+=item location
+
+Gets and sets C<Location> header.
+
+Note that this method doesn't normalize the given URI string in the
+setter. See above in C<redirect> for details.
+
+=item cookies
+
+ $res->cookies->{foo} = 123;
+ $res->cookies->{foo} = { value => '123' };
+
+Returns a hash reference containing cookies to be set in the
+response. The keys of the hash are the cookies' names, and their
+corresponding values are a plain string (for C<value> with everything
+else defaults) or a hash reference that can contain keys such as
+C<value>, C<domain>, C<expires>, C<path>, C<httponly>, C<secure>.
+
+C<expires> can take a string or an integer (as an epoch time) and
+B<does not> convert string formats such as C<+3M>.
+
+ $res->cookies->{foo} = {
+ value => 'test',
+ path => "/",
+ domain => '.example.com',
+ expires => time + 24 * 60 * 60,
+ };
+
+=item finalize
+
+ $res->finalize;
+
+Returns the status code, headers, and body of this response as a PSGI
+response array reference.
+
+=back
+
+=head1 AUTHOR
+
+Tokuhiro Matsuno
+
+Tatsuhiko Miyagawa
+
+=head1 SEE ALSO
+
+L<Plack::Request>
+
+=cut