mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 09:40:21 +00:00
310 lines
7.2 KiB
Perl
310 lines
7.2 KiB
Perl
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
|