Search code examples
perlmojolicious

How do I create a request from a parsed string with Mojo::UserAgent


My goal is to:

  • create a valid transaction by hand with Mojo::UserAgent
  • once this works, dump the request to a text file using $tx->req->to_string
  • then re-use the transaction by creating a new one via parsing the file's contents

The re-use program looks like this:

use Mojo::Util qw/dumper/;
use Mojo::File qw/path/;
use Mojo::UserAgent;
use Mojo::Message::Request;
use Mojo::Transaction::HTTP;

my $req_string = path($ARGV[0])->slurp;

my $ua = Mojo::UserAgent->new;
my $tx = Mojo::Transaction::HTTP->new;

$tx->req->parse($req_string);

print dumper $tx->req; # seems to print a valid Mojo::Message::Request object

$tx = $ua->start($tx);
# this fails with Can't call method "server" on an undefined value at /home/me/perl5/perlbrew/perls/perl-5.30.2/lib/site_perl/5.30.2/Mojo/Server/Daemon.pm line 55.

print dumper $tx->res->body;

which fails at line 16 with

Can't call method "server" on an undefined value at /home/me/perl5/perlbrew/perls/perl-5.30.2/lib/site_perl/5.30.2/Mojo/Server/Daemon.pm line 55.

In general, this seems to fail:

use Mojo::UserAgent;
    
my $ua = Mojo::UserAgent->new;
my $tx = Mojo::Transaction::HTTP->new;

# more transaction defining stuff

$tx = $ua->start($tx);
print $tx->res->body; # success!

my $txt = $tx->req->to_string;

$tx = Mojo::Transaction::HTTP->new;
$tx->req->parse($txt);
$tx = $ua->start($tx); # failure!
print $tx->res->body;

What am I doing wrong?


Solution

  • I have no idea whether this is a bug or not. This seems to fix the problem:

        $tx = 'Mojo::Transaction::HTTP'->new;
        $tx->req->parse($txt);
        $tx->req->url($tx->req->url->to_abs);
        $tx = $ua->start($tx);  # No more failures!
    

    Why does this help?

    If you added Carp::Always, you'd see that the error comes from line 317 in Mojo::UserAgent:

      if (!$url->is_abs && (my $server = $self->server)) {
        my $base = $loop == $self->ioloop ? $server->url : $server->nb_url;  # <- LINE 317.
        $url->scheme($base->scheme)->host($base->host)->port($base->port);
      }
    

    How comes the $url is not absolute? Becuase in Mojo::URL, is_abs is defined as

    sub is_abs { !!shift->scheme }
    

    And if we inspect the url coming from the request, it indeed doesn't have a scheme:

    #! /usr/bin/perl
    use warnings;
    use strict;
    use feature qw{ say };
    
    use Mojo::Message::Request;
    use Mojo::URL;
    
    my $url1 = 'Mojo::URL'->new('http://example.com');
    say $url1->to_string, ' ', $url1->is_abs ? 'abs' : '!abs';  # Is absolute.
    
    my $txt = join "\r\n", 'GET / HTTP/1.1', 'Host: example.com', "", "";
    my $url2 = 'Mojo::Message::Request'->new->parse($txt)->url;
    say $url2->to_string, ' ', $url2->is_abs ? 'abs' : '!abs';  # Isn't absolute!
    print Dumper $url1, $url2;
    

    You can see the difference in the Data::Dumper output:

    $VAR1 = bless( {
                     'query' => bless( {
                                         'pairs' => [],
                                         'charset' => 'UTF-8'
                                       }, 'Mojo::Parameters' ),
                     'path' => bless( {
                                        'parts' => [],
                                        'leading_slash' => '',
                                        'charset' => 'UTF-8',
                                        'trailing_slash' => ''
                                      }, 'Mojo::Path' ),
                     'scheme' => 'http',
                     'host' => 'example.com'
                   }, 'Mojo::URL' );
    $VAR1 = bless( {
                     'path' => bless( {
                                        'charset' => 'UTF-8',
                                        'path' => '/'
                                      }, 'Mojo::Path' ),
                     'query' => bless( {
                                         'charset' => 'UTF-8',
                                         'pairs' => []
                                       }, 'Mojo::Parameters' ),
                     'base' => bless( {
                                        'scheme' => 'http',
                                        'host' => 'example.com'
                                      }, 'Mojo::URL' )
                   }, 'Mojo::URL' );
    

    That's also why the hack above fixes the problem: it sets the missing scheme (and host) in the second URL.

    Let's now wait for someone more Mojo-savvy to explain all this to us.