aboutsummaryrefslogtreecommitdiff
path: root/samples/Perl/Request.pm
diff options
context:
space:
mode:
Diffstat (limited to 'samples/Perl/Request.pm')
-rw-r--r--samples/Perl/Request.pm695
1 files changed, 695 insertions, 0 deletions
diff --git a/samples/Perl/Request.pm b/samples/Perl/Request.pm
new file mode 100644
index 0000000..23d2d3b
--- /dev/null
+++ b/samples/Perl/Request.pm
@@ -0,0 +1,695 @@
+package Plack::Request;
+use strict;
+use warnings;
+use 5.008_001;
+our $VERSION = '0.9988';
+$VERSION = eval $VERSION;
+
+use HTTP::Headers;
+use Carp ();
+use Hash::MultiValue;
+use HTTP::Body;
+
+use Plack::Request::Upload;
+use Plack::TempBuffer;
+use URI;
+use URI::Escape ();
+
+sub _deprecated {
+ my $alt = shift;
+ my $method = (caller(1))[3];
+ Carp::carp("$method is deprecated. Use '$alt' instead.");
+}
+
+sub new {
+ my($class, $env) = @_;
+ Carp::croak(q{$env is required})
+ unless defined $env && ref($env) eq 'HASH';
+
+ bless { env => $env }, $class;
+}
+
+sub env { $_[0]->{env} }
+
+sub address { $_[0]->env->{REMOTE_ADDR} }
+sub remote_host { $_[0]->env->{REMOTE_HOST} }
+sub protocol { $_[0]->env->{SERVER_PROTOCOL} }
+sub method { $_[0]->env->{REQUEST_METHOD} }
+sub port { $_[0]->env->{SERVER_PORT} }
+sub user { $_[0]->env->{REMOTE_USER} }
+sub request_uri { $_[0]->env->{REQUEST_URI} }
+sub path_info { $_[0]->env->{PATH_INFO} }
+sub path { $_[0]->env->{PATH_INFO} || '/' }
+sub script_name { $_[0]->env->{SCRIPT_NAME} }
+sub scheme { $_[0]->env->{'psgi.url_scheme'} }
+sub secure { $_[0]->scheme eq 'https' }
+sub body { $_[0]->env->{'psgi.input'} }
+sub input { $_[0]->env->{'psgi.input'} }
+
+sub content_length { $_[0]->env->{CONTENT_LENGTH} }
+sub content_type { $_[0]->env->{CONTENT_TYPE} }
+
+sub session { $_[0]->env->{'psgix.session'} }
+sub session_options { $_[0]->env->{'psgix.session.options'} }
+sub logger { $_[0]->env->{'psgix.logger'} }
+
+sub cookies {
+ my $self = shift;
+
+ return {} unless $self->env->{HTTP_COOKIE};
+
+ # HTTP_COOKIE hasn't changed: reuse the parsed cookie
+ if ( $self->env->{'plack.cookie.parsed'}
+ && $self->env->{'plack.cookie.string'} eq $self->env->{HTTP_COOKIE}) {
+ return $self->env->{'plack.cookie.parsed'};
+ }
+
+ $self->env->{'plack.cookie.string'} = $self->env->{HTTP_COOKIE};
+
+ my %results;
+ my @pairs = grep /=/, split "[;,] ?", $self->env->{'plack.cookie.string'};
+ for my $pair ( @pairs ) {
+ # trim leading trailing whitespace
+ $pair =~ s/^\s+//; $pair =~ s/\s+$//;
+
+ my ($key, $value) = map URI::Escape::uri_unescape($_), split( "=", $pair, 2 );
+
+ # Take the first one like CGI.pm or rack do
+ $results{$key} = $value unless exists $results{$key};
+ }
+
+ $self->env->{'plack.cookie.parsed'} = \%results;
+}
+
+sub query_parameters {
+ my $self = shift;
+ $self->env->{'plack.request.query'} ||= Hash::MultiValue->new($self->uri->query_form);
+}
+
+sub content {
+ my $self = shift;
+
+ unless ($self->env->{'psgix.input.buffered'}) {
+ $self->_parse_request_body;
+ }
+
+ my $fh = $self->input or return '';
+ my $cl = $self->env->{CONTENT_LENGTH} or return'';
+ $fh->read(my($content), $cl, 0);
+ $fh->seek(0, 0);
+
+ return $content;
+}
+
+sub raw_body { $_[0]->content }
+
+# XXX you can mutate headers with ->headers but it's not written through to the env
+
+sub headers {
+ my $self = shift;
+ if (!defined $self->{headers}) {
+ my $env = $self->env;
+ $self->{headers} = HTTP::Headers->new(
+ map {
+ (my $field = $_) =~ s/^HTTPS?_//;
+ ( $field => $env->{$_} );
+ }
+ grep { /^(?:HTTP|CONTENT|COOKIE)/i } keys %$env
+ );
+ }
+ $self->{headers};
+}
+
+sub content_encoding { shift->headers->content_encoding(@_) }
+sub header { shift->headers->header(@_) }
+sub referer { shift->headers->referer(@_) }
+sub user_agent { shift->headers->user_agent(@_) }
+
+sub body_parameters {
+ my $self = shift;
+
+ unless ($self->env->{'plack.request.body'}) {
+ $self->_parse_request_body;
+ }
+
+ return $self->env->{'plack.request.body'};
+}
+
+# contains body + query
+sub parameters {
+ my $self = shift;
+
+ $self->env->{'plack.request.merged'} ||= do {
+ my $query = $self->query_parameters;
+ my $body = $self->body_parameters;
+ Hash::MultiValue->new($query->flatten, $body->flatten);
+ };
+}
+
+sub uploads {
+ my $self = shift;
+
+ if ($self->env->{'plack.request.upload'}) {
+ return $self->env->{'plack.request.upload'};
+ }
+
+ $self->_parse_request_body;
+ return $self->env->{'plack.request.upload'};
+}
+
+sub hostname { _deprecated 'remote_host'; $_[0]->remote_host || $_[0]->address }
+sub url_scheme { _deprecated 'scheme'; $_[0]->scheme }
+sub params { _deprecated 'parameters'; shift->parameters(@_) }
+sub query_params { _deprecated 'query_parameters'; shift->query_parameters(@_) }
+sub body_params { _deprecated 'body_parameters'; shift->body_parameters(@_) }
+
+sub cookie {
+ my $self = shift;
+ _deprecated 'cookies';
+
+ return keys %{ $self->cookies } if @_ == 0;
+
+ my $name = shift;
+ return $self->cookies->{$name};
+}
+
+sub param {
+ my $self = shift;
+
+ return keys %{ $self->parameters } if @_ == 0;
+
+ my $key = shift;
+ return $self->parameters->{$key} unless wantarray;
+ return $self->parameters->get_all($key);
+}
+
+sub upload {
+ my $self = shift;
+
+ return keys %{ $self->uploads } if @_ == 0;
+
+ my $key = shift;
+ return $self->uploads->{$key} unless wantarray;
+ return $self->uploads->get_all($key);
+}
+
+sub raw_uri {
+ my $self = shift;
+ _deprecated 'base';
+
+ my $base = $self->base;
+ $base->path_query($self->env->{REQUEST_URI});
+
+ $base;
+}
+
+sub uri {
+ my $self = shift;
+
+ my $base = $self->_uri_base;
+
+ # We have to escape back PATH_INFO in case they include stuff like
+ # ? or # so that the URI parser won't be tricked. However we should
+ # preserve '/' since encoding them into %2f doesn't make sense.
+ # This means when a request like /foo%2fbar comes in, we recognize
+ # it as /foo/bar which is not ideal, but that's how the PSGI PATH_INFO
+ # spec goes and we can't do anything about it. See PSGI::FAQ for details.
+ # http://github.com/miyagawa/Plack/issues#issue/118
+ my $path_escape_class = '^A-Za-z0-9\-\._~/';
+
+ my $path = URI::Escape::uri_escape($self->env->{PATH_INFO} || '', $path_escape_class);
+ $path .= '?' . $self->env->{QUERY_STRING}
+ if defined $self->env->{QUERY_STRING} && $self->env->{QUERY_STRING} ne '';
+
+ $base =~ s!/$!! if $path =~ m!^/!;
+
+ return URI->new($base . $path)->canonical;
+}
+
+sub base {
+ my $self = shift;
+ URI->new($self->_uri_base)->canonical;
+}
+
+sub _uri_base {
+ my $self = shift;
+
+ my $env = $self->env;
+
+ my $uri = ($env->{'psgi.url_scheme'} || "http") .
+ "://" .
+ ($env->{HTTP_HOST} || (($env->{SERVER_NAME} || "") . ":" . ($env->{SERVER_PORT} || 80))) .
+ ($env->{SCRIPT_NAME} || '/');
+
+ return $uri;
+}
+
+sub new_response {
+ my $self = shift;
+ require Plack::Response;
+ Plack::Response->new(@_);
+}
+
+sub _parse_request_body {
+ my $self = shift;
+
+ my $ct = $self->env->{CONTENT_TYPE};
+ my $cl = $self->env->{CONTENT_LENGTH};
+ if (!$ct && !$cl) {
+ # No Content-Type nor Content-Length -> GET/HEAD
+ $self->env->{'plack.request.body'} = Hash::MultiValue->new;
+ $self->env->{'plack.request.upload'} = Hash::MultiValue->new;
+ return;
+ }
+
+ my $body = HTTP::Body->new($ct, $cl);
+
+ # HTTP::Body will create temporary files in case there was an
+ # upload. Those temporary files can be cleaned up by telling
+ # HTTP::Body to do so. It will run the cleanup when the request
+ # env is destroyed. That the object will not go out of scope by
+ # the end of this sub we will store a reference here.
+ $self->env->{'plack.request.http.body'} = $body;
+ $body->cleanup(1);
+
+ my $input = $self->input;
+
+ my $buffer;
+ if ($self->env->{'psgix.input.buffered'}) {
+ # Just in case if input is read by middleware/apps beforehand
+ $input->seek(0, 0);
+ } else {
+ $buffer = Plack::TempBuffer->new($cl);
+ }
+
+ my $spin = 0;
+ while ($cl) {
+ $input->read(my $chunk, $cl < 8192 ? $cl : 8192);
+ my $read = length $chunk;
+ $cl -= $read;
+ $body->add($chunk);
+ $buffer->print($chunk) if $buffer;
+
+ if ($read == 0 && $spin++ > 2000) {
+ Carp::croak "Bad Content-Length: maybe client disconnect? ($cl bytes remaining)";
+ }
+ }
+
+ if ($buffer) {
+ $self->env->{'psgix.input.buffered'} = 1;
+ $self->env->{'psgi.input'} = $buffer->rewind;
+ } else {
+ $input->seek(0, 0);
+ }
+
+ $self->env->{'plack.request.body'} = Hash::MultiValue->from_mixed($body->param);
+
+ my @uploads = Hash::MultiValue->from_mixed($body->upload)->flatten;
+ my @obj;
+ while (my($k, $v) = splice @uploads, 0, 2) {
+ push @obj, $k, $self->_make_upload($v);
+ }
+
+ $self->env->{'plack.request.upload'} = Hash::MultiValue->new(@obj);
+
+ 1;
+}
+
+sub _make_upload {
+ my($self, $upload) = @_;
+ my %copy = %$upload;
+ $copy{headers} = HTTP::Headers->new(%{$upload->{headers}});
+ Plack::Request::Upload->new(%copy);
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Plack::Request - Portable HTTP request object from PSGI env hash
+
+=head1 SYNOPSIS
+
+ use Plack::Request;
+
+ my $app_or_middleware = sub {
+ my $env = shift; # PSGI env
+
+ my $req = Plack::Request->new($env);
+
+ my $path_info = $req->path_info;
+ my $query = $req->param('query');
+
+ my $res = $req->new_response(200); # new Plack::Response
+ $res->finalize;
+ };
+
+=head1 DESCRIPTION
+
+L<Plack::Request> provides a consistent API for request objects across
+web server environments.
+
+=head1 CAVEAT
+
+Note that this module is intended to be used by Plack middleware
+developers and web application framework developers rather than
+application developers (end users).
+
+Writing your web application directly using Plack::Request is
+certainly possible but not recommended: it's like doing so with
+mod_perl's Apache::Request: yet too low level.
+
+If you're writing a web application, not a framework, then you're
+encouraged to use one of the web application frameworks that support PSGI (L<http://plackperl.org/#frameworks>),
+or see modules like L<HTTP::Engine> to provide higher level
+Request and Response API on top of PSGI.
+
+=head1 METHODS
+
+Some of the methods defined in the earlier versions are deprecated in
+version 0.99. Take a look at L</"INCOMPATIBILITIES">.
+
+Unless otherwise noted, all methods and attributes are B<read-only>,
+and passing values to the method like an accessor doesn't work like
+you expect it to.
+
+=head2 new
+
+ Plack::Request->new( $env );
+
+Creates a new request object.
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item env
+
+Returns the shared PSGI environment hash reference. This is a
+reference, so writing to this environment passes through during the
+whole PSGI request/response cycle.
+
+=item address
+
+Returns the IP address of the client (C<REMOTE_ADDR>).
+
+=item remote_host
+
+Returns the remote host (C<REMOTE_HOST>) of the client. It may be
+empty, in which case you have to get the IP address using C<address>
+method and resolve on your own.
+
+=item method
+
+Contains the request method (C<GET>, C<POST>, C<HEAD>, etc).
+
+=item protocol
+
+Returns the protocol (HTTP/1.0 or HTTP/1.1) used for the current request.
+
+=item request_uri
+
+Returns the raw, undecoded request URI path. You probably do B<NOT>
+want to use this to dispatch requests.
+
+=item path_info
+
+Returns B<PATH_INFO> in the environment. Use this to get the local
+path for the requests.
+
+=item path
+
+Similar to C<path_info> but returns C</> in case it is empty. In other
+words, it returns the virtual path of the request URI after C<<
+$req->base >>. See L</"DISPATCHING"> for details.
+
+=item script_name
+
+Returns B<SCRIPT_NAME> in the environment. This is the absolute path
+where your application is hosted.
+
+=item scheme
+
+Returns the scheme (C<http> or C<https>) of the request.
+
+=item secure
+
+Returns true or false, indicating whether the connection is secure (https).
+
+=item body, input
+
+Returns C<psgi.input> handle.
+
+=item session
+
+Returns (optional) C<psgix.session> hash. When it exists, you can
+retrieve and store per-session data from and to this hash.
+
+=item session_options
+
+Returns (optional) C<psgix.session.options> hash.
+
+=item logger
+
+Returns (optional) C<psgix.logger> code reference. When it exists,
+your application is supposed to send the log message to this logger,
+using:
+
+ $req->logger->({ level => 'debug', message => "This is a debug message" });
+
+=item cookies
+
+Returns a reference to a hash containing the cookies. Values are
+strings that are sent by clients and are URI decoded.
+
+=item query_parameters
+
+Returns a reference to a hash containing query string (GET)
+parameters. This hash reference is L<Hash::MultiValue> object.
+
+=item body_parameters
+
+Returns a reference to a hash containing posted parameters in the
+request body (POST). As with C<query_parameters>, the hash
+reference is a L<Hash::MultiValue> object.
+
+=item parameters
+
+Returns a L<Hash::MultiValue> hash reference containing (merged) GET
+and POST parameters.
+
+=item content, raw_body
+
+Returns the request content in an undecoded byte string for POST requests.
+
+=item uri
+
+Returns an URI object for the current request. The URI is constructed
+using various environment values such as C<SCRIPT_NAME>, C<PATH_INFO>,
+C<QUERY_STRING>, C<HTTP_HOST>, C<SERVER_NAME> and C<SERVER_PORT>.
+
+Every time this method is called it returns a new, cloned URI object.
+
+=item base
+
+Returns an URI object for the base path of current request. This is
+like C<uri> but only contains up to C<SCRIPT_NAME> where your
+application is hosted at.
+
+Every time this method is called it returns a new, cloned URI object.
+
+=item user
+
+Returns C<REMOTE_USER> if it's set.
+
+=item headers
+
+Returns an L<HTTP::Headers> object containing the headers for the current request.
+
+=item uploads
+
+Returns a reference to a hash containing uploads. The hash reference
+is a L<Hash::MultiValue> object and values are L<Plack::Request::Upload>
+objects.
+
+=item content_encoding
+
+Shortcut to $req->headers->content_encoding.
+
+=item content_length
+
+Shortcut to $req->headers->content_length.
+
+=item content_type
+
+Shortcut to $req->headers->content_type.
+
+=item header
+
+Shortcut to $req->headers->header.
+
+=item referer
+
+Shortcut to $req->headers->referer.
+
+=item user_agent
+
+Shortcut to $req->headers->user_agent.
+
+=item param
+
+Returns GET and POST parameters with a CGI.pm-compatible param
+method. This is an alternative method for accessing parameters in
+$req->parameters. Unlike CGI.pm, it does I<not> allow
+setting or modifying query parameters.
+
+ $value = $req->param( 'foo' );
+ @values = $req->param( 'foo' );
+ @params = $req->param;
+
+=item upload
+
+A convenient method to access $req->uploads.
+
+ $upload = $req->upload('field');
+ @uploads = $req->upload('field');
+ @fields = $req->upload;
+
+ for my $upload ( $req->upload('field') ) {
+ print $upload->filename;
+ }
+
+=item new_response
+
+ my $res = $req->new_response;
+
+Creates a new L<Plack::Response> object. Handy to remove dependency on
+L<Plack::Response> in your code for easy subclassing and duck typing
+in web application frameworks, as well as overriding Response
+generation in middlewares.
+
+=back
+
+=head2 Hash::MultiValue parameters
+
+Parameters that can take one or multiple values (i.e. C<parameters>,
+C<query_parameters>, C<body_parameters> and C<uploads>) store the
+hash reference as a L<Hash::MultiValue> object. This means you can use
+the hash reference as a plain hash where values are B<always> scalars
+(B<NOT> array references), so you don't need to code ugly and unsafe
+C<< ref ... eq 'ARRAY' >> anymore.
+
+And if you explicitly want to get multiple values of the same key, you
+can call the C<get_all> method on it, such as:
+
+ my @foo = $req->query_parameters->get_all('foo');
+
+You can also call C<get_one> to always get one parameter independent
+of the context (unlike C<param>), and even call C<mixed> (with
+Hash::MultiValue 0.05 or later) to get the I<traditional> hash
+reference,
+
+ my $params = $req->parameters->mixed;
+
+where values are either a scalar or an array reference depending on
+input, so it might be useful if you already have the code to deal with
+that ugliness.
+
+=head2 PARSING POST BODY and MULTIPLE OBJECTS
+
+The methods to parse request body (C<content>, C<body_parameters> and
+C<uploads>) are carefully coded to save the parsed body in the
+environment hash as well as in the temporary buffer, so you can call
+them multiple times and create Plack::Request objects multiple times
+in a request and they should work safely, and won't parse request body
+more than twice for the efficiency.
+
+=head1 DISPATCHING
+
+If your application or framework wants to dispatch (or route) actions
+based on request paths, be sure to use C<< $req->path_info >> not C<<
+$req->uri->path >>.
+
+This is because C<path_info> gives you the virtual path of the request,
+regardless of how your application is mounted. If your application is
+hosted with mod_perl or CGI scripts, or even multiplexed with tools
+like L<Plack::App::URLMap>, request's C<path_info> always gives you
+the action path.
+
+Note that C<path_info> might give you an empty string, in which case
+you should assume that the path is C</>.
+
+You will also want to use C<< $req->base >> as a base prefix when
+building URLs in your templates or in redirections. It's a good idea
+for you to subclass Plack::Request and define methods such as:
+
+ sub uri_for {
+ my($self, $path, $args) = @_;
+ my $uri = $self->base;
+ $uri->path($uri->path . $path);
+ $uri->query_form(@$args) if $args;
+ $uri;
+ }
+
+So you can say:
+
+ my $link = $req->uri_for('/logout', [ signoff => 1 ]);
+
+and if C<< $req->base >> is C</app> you'll get the full URI for
+C</app/logout?signoff=1>.
+
+=head1 INCOMPATIBILITIES
+
+In version 0.99, many utility methods are removed or deprecated, and
+most methods are made read-only.
+
+The following methods are deprecated: C<hostname>, C<url_scheme>,
+C<params>, C<query_params>, C<body_params>, C<cookie> and
+C<raw_uri>. They will be removed in the next major release.
+
+All parameter-related methods such as C<parameters>,
+C<body_parameters>, C<query_parameters> and C<uploads> now contains
+L<Hash::MultiValue> objects, rather than I<scalar or an array
+reference depending on the user input> which is insecure. See
+L<Hash::MultiValue> for more about this change.
+
+C<< $req->path >> method had a bug, where the code and the document
+was mismatching. The document was suggesting it returns the sub
+request path after C<< $req->base >> but the code was always returning
+the absolute URI path. The code is now updated to be an alias of C<<
+$req->path_info >> but returns C</> in case it's empty. If you need
+the older behavior, just call C<< $req->uri->path >> instead.
+
+Cookie handling is simplified, and doesn't use L<CGI::Simple::Cookie>
+anymore, which means you B<CAN NOT> set array reference or hash
+reference as a cookie value and expect it be serialized. You're always
+required to set string value, and encoding or decoding them is totally
+up to your application or framework. Also, C<cookies> hash reference
+now returns I<strings> for the cookies rather than CGI::Simple::Cookie
+objects, which means you no longer have to write a wacky code such as:
+
+ $v = $req->cookie->{foo} ? $req->cookie->{foo}->value : undef;
+
+and instead, simply do:
+
+ $v = $req->cookie->{foo};
+
+=head1 AUTHORS
+
+Tatsuhiko Miyagawa
+
+Kazuhiro Osawa
+
+Tokuhiro Matsuno
+
+=head1 SEE ALSO
+
+L<Plack::Response> L<HTTP::Request>, L<Catalyst::Request>
+
+=head1 LICENSE
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut