perl Mojo и JSON для одновременных запросов

Обычно я не программист на Perl. Однако я должен выполнить эту задачу.

У меня работает следующий код:

#!/usr/bin/perl

use LWP::UserAgent;
use JSON;
use strict;

my $md5 = $ARGV[0];
$md5 =~ s/[^A-Fa-f0-9 ]*//g;
die "invalid MD5" unless ( length($md5) == 32 );

my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 }, timeout => 10);
my $key="12345...7890";
my $url='https://www.virustotal.com/vtapi/v2/file/report';
my $response = $ua->post( $url, ['apikey' => $key, 'resource' => $md5] );
die "$url error: ", $response->status_line unless $response->is_success;
my $results=$response->content;

my $json = JSON->new->allow_nonref;
my $decjson = $json->decode( $results);

print "md5: ",$md5,"\n";
print "positives: ", $decjson->{"positives"}, "\n";
print "total: ", $decjson->{"total"}, "\n";
print "date: ", $decjson->{"scan_date"}, "\n";

Теперь я хотел бы перекодировать приведенное выше для использования асинхронного http с помощью Mojo. Я пытаюсь это:

#!/usr/bin/perl

use warnings;
use strict;
use Mojo;
use Mojo::UserAgent;

my $md5 = $ARGV[0];
$md5 =~ s/[^A-Fa-f0-9 ]*//g;
die "invalid MD5" unless ( length($md5) == 32 );

my ($vt_positives, $vt_scandate, $response_vt);
my $url='https://www.virustotal.com/vtapi/v2/file/report';
my $key="12345...7890";
my $ua = Mojo::UserAgent->new;
my $delay = Mojo::IOLoop->delay;

$ua->max_redirects(0)->connect_timeout(3)->request_timeout(6);
$ua->max_redirects(5);
$delay->begin;

$response_vt = $ua->post( $url => ['apikey' => $key, 'resource' => $md5] => sub {
    my ($ua, $tx) = @_;
    $vt_positives=$tx->res->json->{"positives"};
    print "Got response: $vt_positives\n";
    });

Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

Первый код в порядке, второй не работает. Должно быть, я делаю что-то неправильно при отправке запроса, так как, кажется, получаю ответ 403 (неправильное использование API). Я также пробовал -> вызовы json, но это не сработало.

И даже если бы я выполнил запрос правильно, я не уверен, правильно ли я расшифровываю результаты json с помощью Mojo.

Помощь будет оценена!


person user1458620    schedule 01.03.2013    source источник


Ответы (3)


ИЗМЕНИТЬ

Кажется, мы упустили настоящий вопрос, как публиковать формы. Ой извините за это.

Формы публикации зависят от того, какую версию Mojolicious вы используете. До недавнего времени (v3.85 -- 13 февраля 2013 г.) был post_form методом. Однако, поразмыслив, было решено, что либо для каждого типа запроса должно быть *_form метода, либо мы должны сделать что-то более разумное, и, таким образом, form генератор родился.

$response_vt = $ua->post( 
  $url, 
  form => {'apikey' => $key, 'resource' => $md5}, 
  sub { ... }
);

Его можно добавить к любому методу запроса, что сделает его гораздо более согласованным, чем старая форма. Также обратите внимание, что это должен быть hashref, а не arrayref, как позволяет LWP. Кстати, есть также генератор json, который работает так же, или вы даже можете добавь свой!

Я оставляю свой первоначальный ответ, показывающий неблокирующее использование, которое вы теперь можете изменить, учитывая вышеизложенное.

ОРИГИНАЛ

Опираясь на логику креатива, я бы начал так. Основное отличие состоит в том, что нет монитора, наблюдающего за тем, идут ли работы, а когда кто-то заканчивает, он проверяет, чтобы убедиться, что нет бездельников.

Я также внес некоторые изменения в логику синтаксического анализа, но ничего серьезного.

#!/usr/bin/env perl
use Mojo::Base -strict;
use utf8::all;

use Mojo::URL;
use Mojo::UserAgent;

# FIFO queue
my @urls = qw(
    http://sysd.org/page/1/
    http://sysd.org/page/2/
    http://sysd.org/page/3/
);

# User agent following up to 5 redirects
my $ua = Mojo::UserAgent
    ->new(max_redirects => 5)
    ->detect_proxy;

start_urls($ua, \@urls, \&get_callback);

sub start_urls {
  my ($ua, $queue, $cb) = @_;

  # Limit parallel connections to 4
  state $idle = 4;
  state $delay = Mojo::IOLoop->delay(sub{say @$queue ? "Loop ended before queue depleated" : "Finished"});

  while ( $idle and my $url = shift @$queue ) {
    $idle--;
    print "Starting $url, $idle idle\n\n";

    $delay->begin;

    $ua->get($url => sub{ 
      $idle++; 
      print "Got $url, $idle idle\n\n"; 
      $cb->(@_, $queue); 

      # refresh worker pool
      start_urls($ua, $queue, $cb); 
      $delay->end; 
    });

  }

  # Start event loop if necessary
  $delay->wait unless $delay->ioloop->is_running;
}

sub get_callback {
    my ($ua, $tx, $queue) = @_;

    # Parse only OK HTML responses
    return unless 
        $tx->res->is_status_class(200)
        and $tx->res->headers->content_type =~ m{^text/html\b}ix;

    # Request URL
    my $url = $tx->req->url;
    say "Processing $url";
    parse_html($url, $tx, $queue);
}

sub parse_html {
    my ($url, $tx, $queue) = @_;

    state %visited;

    my $dom = $tx->res->dom;
    say $dom->at('html title')->text;

    # Extract and enqueue URLs
    $dom->find('a[href]')->each(sub{

        # Validate href attribute
        my $link = Mojo::URL->new($_->{href});
        return unless eval { $link->isa('Mojo::URL') };

        # "normalize" link
        $link = $link->to_abs($url)->fragment(undef);
        return unless grep { $link->protocol eq $_ } qw(http https);

        # Don't go deeper than /a/b/c
        return if @{$link->path->parts} > 3;

        # Access every link only once
        return if $visited{$link->to_string}++;

        # Don't visit other hosts
        return if $link->host ne $url->host;

        push @$queue, $link;
        say " -> $link";
    });
    say '';

    return;
}
person Joel Berger    schedule 01.03.2013
comment
Очень интересно, правда! Я не думал об использовании delay в качестве условной переменной. Мой recurring на самом деле является быстрым и грязным хаком для эмуляции цикла событий поверх другого цикла событий... Ваше использование state и передача $queue в качестве ссылки также делает код намного чище. - person creaktive; 04.03.2013
comment
Здесь то же самое, что и раньше: хорошо, но не имеет ничего общего с первоначальным вопросом, который был о том, что http POST не работает. Этот код полезен (и так и есть!) только с http GET. - person user1458620; 04.03.2013
comment
О, я думаю, мы неправильно поняли природу вашей неудачи, думая, что это была неблокирующая природа. Позвольте мне прочитать немного, и я обновлю - person Joel Berger; 04.03.2013
comment
Хорошо, спасибо большое, помогло. Я использую OpenBSD. Как вы, возможно, знаете, порты и пакеты OpenBSD не всегда поставляются с последними версиями. OpenBSD52, которую я использую, предоставляет файл p5-Mojolicious-2.76.tgz. Я знаю, что мог бы обновиться до более новых версий, но это несколько противоречило бы нашей политике максимально придерживаться дистрибутива. Тем не менее, мне помог друг, так что вот как это использовать (я предполагаю, что во всех версиях Mojolicious до 3.85): { - person user1458620; 05.03.2013
comment
Я не очень хорошо помню специфику Mojolicious до версии 3.0, поэтому не могу вам помочь. Используйте вашу локальную команду perldoc, чтобы прочитать документацию, поставляемую с вашим пакетом, или используйте Metacpan, чтобы прочитать документацию для данной версии. metacpan.org/module/SRI/Mojolicious-2.76/lib/Mojolicious. вечера - person Joel Berger; 05.03.2013
comment
Тем не менее, есть хорошие новые вкусности для вас, если вы обновитесь! Mojolicious развивается очень быстро. - person Joel Berger; 05.03.2013
comment
говоря о быстром перемещении, $delay->end больше не работает. Нужно использовать my $end = $delay->begin;, а затем вызов $end->() в обратном вызове. Синопсис обновлен, но изменение не задокументировано иным образом, за исключением одной краткой строки в файле изменений. - person plusplus; 18.07.2014
comment
Это правда, что разработка Mojolicious движется быстро. Однако на самом деле я сохранил модульную версию этого ответа, работающую как суть github: gist.github.com/jberger /5153008 - person Joel Berger; 19.07.2014

Взгляните на этот параллельный поисковый робот на основе Mojolicious, который я написал для иллюстрации своей статьи Скрапинг веб-страниц с помощью современного Perl:

#!/usr/bin/env perl
use 5.010;
use open qw(:locale);
use strict;
use utf8;
use warnings qw(all);

use Mojo::UserAgent;

# FIFO queue
my @urls = map { Mojo::URL->new($_) } qw(
    http://sysd.org/page/1/
    http://sysd.org/page/2/
    http://sysd.org/page/3/
);

# Limit parallel connections to 4
my $max_conn = 4;

# User agent following up to 5 redirects
my $ua = Mojo::UserAgent
    ->new(max_redirects => 5)
    ->detect_proxy;

# Keep track of active connections
my $active = 0;

Mojo::IOLoop->recurring(
    0 => sub {
        for ($active + 1 .. $max_conn) {

            # Dequeue or halt if there are no active crawlers anymore
            return ($active or Mojo::IOLoop->stop)
                unless my $url = shift @urls;

            # Fetch non-blocking just by adding
            # a callback and marking as active
            ++$active;
            $ua->get($url => \&get_callback);
        }
    }
);

# Start event loop if necessary
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

sub get_callback {
    my (undef, $tx) = @_;

    # Deactivate
    --$active;

    # Parse only OK HTML responses
    return
        if not $tx->res->is_status_class(200)
        or $tx->res->headers->content_type !~ m{^text/html\b}ix;

    # Request URL
    my $url = $tx->req->url;

    say $url;
    parse_html($url, $tx);

    return;
}

sub parse_html {
    my ($url, $tx) = @_;

    say $tx->res->dom->at('html title')->text;

    # Extract and enqueue URLs
    for my $e ($tx->res->dom('a[href]')->each) {

        # Validate href attribute
        my $link = Mojo::URL->new($e->{href});
        next if 'Mojo::URL' ne ref $link;

        # "normalize" link
        $link = $link->to_abs($tx->req->url)->fragment(undef);
        next unless grep { $link->protocol eq $_ } qw(http https);

        # Don't go deeper than /a/b/c
        next if @{$link->path->parts} > 3;

        # Access every link only once
        state $uniq = {};
        ++$uniq->{$url->to_string};
        next if ++$uniq->{$link->to_string} > 1;

        # Don't visit other hosts
        next if $link->host ne $url->host;

        push @urls, $link;
        say " -> $link";
    }
    say '';

    return;
}
person creaktive    schedule 01.03.2013
comment
Почему повторяющийся? Почему бы просто не запустить n рабочих процессов, а обратный вызов запустить еще один, если очередь не пуста? Возможно, особой разницы нет, и я не специалист по неблокирующей стороне вещей, но мне это кажется более простым. - person Joel Berger; 01.03.2013
comment
Иногда @urls начинается только с одного элемента :) - person creaktive; 01.03.2013
comment
Я разместил фрагмент, основанный на вашем, мне любопытно, каковы ваши комментарии. - person Joel Berger; 02.03.2013
comment
Все это очень хорошо, но не решает проблему ОП. Первоначальная проблема заключается не в том, как выполнить асинхронный http, а в том, как выполнить POST (вместо GET) с Mojo и отправить данные формы. Может показаться, что это простая проблема, но в документации нет примера, и все мои попытки терпят неудачу. - person user1458620; 04.03.2013

LWP::UserAgent принимает аргументы для публикации либо в виде ссылки на массив, либо в формате ссылки на хэш.

http://search.cpan.org/~gaas/libwww-perl-6.04/lib/LWP/UserAgent.pm#REQUEST_METHODS

$ua->post( $url, \%form )
$ua->post( $url, \@form )

который вы предоставляете в первом скрипте в формате ссылки на массив "\@form"

my $response = $ua->post( $url, ['apikey' => $key, 'resource' => $md5] );

так как это хэш, вероятно, лучше писать в формате хэша "\%form"

my $response = $ua->post( $url, {'apikey' => $key, 'resource' => $md5} );

В Mojo::UserAgent аргументы для публикации немного сложнее, но, по сути, представляют собой «строку» хэш-ссылок на хэш-ключи, с которыми я не знаком. Однако вы можете обнаружить, что использование формата хэш-ссылки правильно обеспечивает ожидаемые аргументы.

http://search.cpan.org/~sri/Mojolicious-3.87/lib/Mojo/UserAgent.pm#post

POST

my $tx = $ua->post('kraih.com');
my $tx = $ua->post('http://kraih.com' => {DNT => 1} => 'Hi!');
my $tx = $ua->post('http://kraih.com' => {DNT => 1} => form => {a => 'b'});
my $tx = $ua->post('http://kraih.com' => {DNT => 1} => json => {a => 'b'});

попробуй это ?:

$response_vt = $ua->post( $url => form => {'apikey' => $key, 'resource' => $md5} => sub {... });
person doncoyote    schedule 01.03.2013
comment
Я ценю попытку, но, к сожалению, это тоже не сработало. Изменение команды post на версию с использованием фигурных скобок ничего не изменило. Все равно получаю ошибку 403. Я также пробовал $response_vt = $ua-›post( $url =› form =› {'apikey' =› $key, 'resource' =› $md5} =› sub {...} безуспешно. - person user1458620; 01.03.2013
comment
извините, это не помогло, поскольку вы не знакомы с цепочкой хеш-элементов в аргументах. Я надеялся, что исправление синтаксиса поможет. ох, повесьте на орграфе просто причудливую запятую => верно? - person doncoyote; 02.03.2013
comment
хэш-ссылка без ключа генератора (form или json) используется в качестве заголовков. Я отредактировал этот ответ для правильного использования. Однако обратите внимание, что генератор относительно недавний, до этого использовался метод post_form. - person Joel Berger; 04.03.2013
comment
и да, => называется толстой запятой, которая является просто запятой, но также будет цитировать левый аргумент, ЕСЛИ он выглядит как слово. - person Joel Berger; 04.03.2013