mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +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
 |