http://www.perlmonks.org?node_id=854115


in reply to Does CGI::Application expose a HTTP::Request object?

Untested but I think this should work. Hopefully it atleast gives you something to build on.

# Add this to your CGI::Application subclass. sub request_from_cgi { my ($self) = @_; my $q = $self->query; my $uri = $q->url; my $method = uc $q->request_method; my $headers = $q->header($self->header_props); # does this cover all cases??? Perhaps it ought to # look at content-type? if ($method eq 'POST') { $content = $q->param('POSTDATA'); } elsif ($method eq 'PUT') { $content = $q->param('PUTDATA'); } else { $content = undef; } return HTTP::Request->new( $method, $uri, $headers, $content, ); }

--
જલધર

Replies are listed 'Best First'.
Re^2: Does CGI::Application expose a HTTP::Request object?
by isync (Hermit) on Aug 10, 2010 at 23:54 UTC
    I'm going to test this out soon.
    But I can already say that you seem to have solved some of the details I couldn't work out, for example: Where is the $uri value? Where did the $content end up? ..

    Update:
    The value of $header doesn't seem to work for HTTP::Request, it complains with "Bad header argument". Further, it doesn't seem to hold the headers of the request, but of the response CGI::Application is about to deliver, for example in my test it held a "Content-Type: text/html" header.

    Your elaborate attempts to get the $content *of the request* do not seem to work. None of my tests showed the request content getting through. my @params = $q->param should return all parameters but is empty except for those keys passed in $ENV{QUERY_STRING}, no PUTDATA etc. there.
    Is a CGI::Fast + CGI::Application based script aware of request data which is not coming from POST requests - at all?

      The value of $header doesn't seem to work for HTTP::Request, it complains with "Bad header argument". Further, it doesn't seem to hold the headers of the request, but of the response CGI::Application is about to deliver, for example in my test it held a "Content-Type: text/html" header.

      That should have been @{ $self->header_props } because HTTP::Request wants a reference to an array of key-value pairs but in any case it wouldn't work because as you pointed out, it gives outgoing headers not incoming.

      You would have to get the special HTTP envvars out of the environment and make them into headers.

      Your elaborate attempts to get the $content *of the request* do not seem to work. None of my tests showed the request content getting through. my @params = $q->param should return all parameters but is empty except for those keys passed in $ENV{QUERY_STRING}, no PUTDATA etc. there. Is a CGI::Fast + CGI::Application based script aware of request data which is not coming from POST requests - at all?

      As I alluded to in my last reply, I think the problem is POSTDATA and PUTDATA don't get set in some cases. (They are not real params but specially set up by CGI.pm) I think this happens if the MIME type is application/x-www-form-urlencoded or multipart/form-data which is usual for an HTML form. So in this case or if we can't get the them for any other reason, we should read from standard input based on the Content-Length header (which doesn't seem to have CGI.pm method I don't know why.)

      Try this. Again untested I'm afraid.

      sub request_from_cgi { my ($self) = @_; my $q = $self->query; my $uri = $q->url; my $method = uc $q->request_method; # probably not an exhaustive list. my @http_envvars = qw/ DOCUMENT_ROOT HTTP_COOKIE HTTP_REFERER HTTP_USER_AGENT HTTPS PATH QUERY_STRING REMOTE_ADDR REMOTE_HOST REMOTE_PORT REMOTE_USER REQUEST_METHOD REQUEST_URI SCRIPT_FILENAME SCRIPT_NAME SERVER_ADMIN SERVER_NAME SERVER_PORT SERVER_SOFTWARE /; my $headers = {}; foreach my $var (@http_envvars) { if (exists $ENV{$var}) { $headers->{$var} = $ENV{$var}; } } my $content = undef; if ($method eq 'POST') { $content = $q->param('POSTDATA'); } elsif ($method eq 'PUT') { $content = $q->param('PUTDATA'); } if (!defined $content) { my $len = $ENV{CONTENT_LENGTH} || 0; read STDIN, $content, $len; } return HTTP::Request->new( $method, $uri, HTTP::Headers->new($headers), $content, ); }

      --
      જલધર

        1. Error on a first pass: "Can't call method "clone" on unblessed reference at /usr/share/perl5/HTTP/Message.pm line 32."

        2. Shouldn't the %ENV variable names be mapped to the a-bit-different HTTP Header names?

        3. And we are passing a hashref to the HTTP::Request constructor, but it expects an arrayref (solved via this node).

        Changed code:
        my %env_to_httpheader = ( 'HTTP_USER_AGENT' => 'User-Agent', 'HTTP_HOST' => 'Host', 'HTTP_TE' => 'Accept-Encoding', 'CONTENT_LENGTH' => 'Content-Length', # from here on blurry from memory: (and probably more to inclu +de as well..) 'CONTENT_ENCODING' => 'Content-Encoding', 'CONTENT_TYPE' => 'Content-Type', ); my $headers; foreach my $key (keys %env_to_httpheader) { if (exists $ENV{$key}) { # $headers->{ $env_to_httpheader{$key} } = $ENV{$key}; # "The optional $header argument should be a reference + to an HTTP::Headers object or a plain array reference of key/value p +airs." push(@{ $headers }, $env_to_httpheader{$key}); push(@{ $headers }, $ENV{$key}); } }

        Result: After changing this, it works!

        Any leftovers, hints, tweaks for this piece of code before I move it to production code? For example, a module for the ENV to HTTP Header translation, or similar improvements?

        2c: A lengthy hack, isn't it? Just to get a HTTP::Response out of CGI.pm/CGI::Fast. Once more, I dislike this CGI.pm-behemoth..