2012-02-28 17 views
5

utilizo WWW :: Curl para subir archivos:www :: rizo - cómo cargar (post) archivos de gran tamaño

use WWW::Curl::Easy 4.14; 
use WWW::Curl::Form; 

my $url = 'http://example.com/backups/?sid=12313qwed323'; 
my $params = { 
    name => 'upload', 
    action => 'keep', 
    backup1 => [ '/tmp/backup1.zip' ], # 1st file for upload 
}; 

my $form = WWW::Curl::Form->new(); 
foreach my $k (keys %{$params}) { 
    if (ref $params->{$k}) { 
     $form->formaddfile(@{$params->{$k}}[0], $k, 'multipart/form-data'); 
    } else { 
     $form->formadd($k, $params->{$k}); 
    } 
} 

my $curl = WWW::Curl::Easy->new() or die $!; 
$curl->setopt(CURLOPT_HTTPPOST, $form); 
$curl->setopt(CURLOPT_URL, $url); 

my $body; 
$curl->setopt(CURLOPT_WRITEDATA, \$body); 
my $retcode = $curl->perform(); 
my $response_code = $curl->getinfo(CURLINFO_HTTP_CODE); 

tiene nada de especial y este código funciona bien.

Quiero cargar archivos de gran tamaño y no quiero cargar todo en la memoria. Al menos eso es lo que escuché que libcurl está haciendo.

CURLOPT_READFUNCTION acepta devoluciones de llamada que devuelve partes del contenido. Eso significa que no puedo usar WWW :: Curl :: Form para establecer los parámetros de POST, pero que tengo que devolver todo el contenido a través de esta devolución de llamada. ¿Está bien?

creo que el código podría tener este aspecto:

use WWW::Curl::Easy 4.14; 

my $url = 'http://example.com/backups/?sid=12313qwed323' 
my $params = { 
    name => 'upload', 
    action => 'keep', 
    backup1 => [ '/tmp/backup1.zip' ], # 1st file for upload 
}; 

my $fields; 
foreach my $k (keys %{$params}) { 
    $fields .= "$k=".(ref $params->{$k} ? '@'[email protected]{$params->{$k}}[0] : uri_escape_utf8($params->{$k}))."&"; 
} 
chop($fields); 

my $curl = WWW::Curl::Easy->new() or die $!; 
$curl->setopt(CURLOPT_POST, 1); 
$curl->setopt(CURLOPT_POSTFIELDS, $fields); # is it needed with READFUNCTION?? 
$curl->setopt(CURLOPT_URL, $url); 

my @header = ('Content-type: multipart/form-data', 'Transfer-Encoding: chunked'); 
$curl->setopt(CURLOPT_HTTPHEADER, \@header); 

#$curl->setopt(CURLOPT_INFILESIZE, $size); 
$curl->setopt(CURLOPT_READFUNCTION, sub { 

    # which data to return here? 
    # $params (without file) + file content? 

    return 0; 
}); 

¿Qué datos de devolución de llamada CURLOPT_READFUNCTION tiene que volver? $ params + archivo (s) de contenido? ¿En que formato?

¿Realmente tengo que crear los datos (devueltos por CURLOPT_READFUNCTION) por mi cuenta o hay una manera simple de crearlos en el formato correcto?

Gracias

+0

¿Está comprometido con el uso de WWW :: Curl? Creo que esto sería más fácil con LWP, si puedes cambiar. – wes

+0

LWP o mejor uso WWW :: Mechanize –

+0

Sé que esta respuesta no está directamente relacionada con su código, pero pasé una gran cantidad de tiempo solucionando un problema similar usando WWW :: Mechanize solo para descubrir que MaxPostSize en el servidor web tenía sido establecido por nuestro administrador a algún límite arbitrario. – AWT

Respuesta

4

prueba 16formpost.t es relevante. Como puede ver, está completamente deshabilitado. Este hecho y mis experimentos infructuosos con varios valores de retorno para la función de devolución de llamada me permiten creer que la característica CURLOPT_READFUNCTION se conoce como rota en el enlace de Perl.

Tengo que devolver todo el contenido a través de esta devolución de llamada. ¿Está bien?

No, puede alimentar el cuerpo de la solicitud a trozos, adecuado para la codificación fragmentada. La devolución de llamada se llamará necesariamente varias veces, de acuerdo con el límite establecido en CURLOPT_INFILESIZE.

¿Qué datos tiene que devolver la devolución de llamada CURLOPT_READFUNCTION?

Un cuerpo de solicitud de HTTP. Como haces una carga de archivos, esto significa Content-Type multipart/form-data. A continuación se muestra un ejemplo usando HTTP :: Message. CURLOPT_HTTPPOST es otra forma de construir este formato.

use HTTP::Request::Common qw(POST); 
use WWW::Curl::Easy 4.14; 

my $curl = WWW::Curl::Easy->new or die $!; 
$curl->setopt(CURLOPT_POST, 1); 
$curl->setopt(CURLOPT_URL, 'http://localhost:5000'); 
$curl->setopt(CURLOPT_HTTPHEADER, [ 
    'Content-type: multipart/form-data', 'Transfer-Encoding: chunked' 
]); 
$curl->setopt(CURLOPT_READFUNCTION, sub { 
    return POST(undef, Content_Type => 'multipart/form-data', Content => [ 
     name => 'upload', 
     action => 'keep', 
     backup1 => [ '/tmp/backup1.zip' ], # 1st file for upload 
    ])->content; 
}); 
my $r = $curl->perform; 
+0

gracias. información muy útil. Problema con HTTP :: Message es que carga todo el contenido en la memoria. Tengo memoria limitada (64 MB) y eso significaría que la solicitud debe ser mucho más pequeña. Hoy intentaré crear una función de devolución de llamada que devuelva el contenido en fragmentos sin almacenar todo el contenido. – toktok

+0

acaba de recibir una palabra del mantenedor de WWW :: Curl sobre la función CURLOPT_READFUNCTION: "sí, parece bastante roto. Estoy planeando hacer una revisión de WWW :: Curl en las próximas semanas, podría arreglar esto también". – daxim

+0

¿Por qué crees que está roto? No tengo problemas con READFUNCTION, se comporta exactamente como se explica en la página principal de curl. "funciona para mí" ;-) – toktok

3

La devolución de llamada CURLOPT_READFUNCTION sólo se utiliza para fragmentada codificación tranfer. Es puede funcionar, pero no he podido conseguirlo y descubrí que hacerlo no era necesario de todos modos.

Mi caso de uso fue para cargar datos a AWS, donde no está bien cargar los datos como datos de formularios de varias partes. En cambio, es un POST directo de los datos. Sin embargo, sí requiere que sepa cuántos datos está enviando al servidor. Esto parece funcionar para mí:

my $infile = 'file-to-upload.json'; 
my $size = -s $infile; 
open(IN, $infile) or die("Cannot open file - $infile. $! \n"); 

my $curl = WWW::Curl::Easy->new; 
$curl->setopt(CURLOPT_HEADER,  1); 
$curl->setopt(CURLOPT_NOPROGRESS, 1); 
$curl->setopt(CURLOPT_POST,   1); 
$curl->setopt(CURLOPT_URL,   $myPostUrl); 
$curl->setopt(CURLOPT_HTTPHEADER, 
    ['Content-Type: application/json']); #For my use case 
$curl->setopt(CURLOPT_POSTFIELDSIZE_LARGE, $size); 
$curl->setopt(CURLOPT_READDATA, \*IN); 

my $retcode = $curl->perform; 

if ($retcode == 0) { 
    print("File upload success\n"); 
} 
else { 
    print("An error happened: $retcode ".$curl->strerror($retcode)."\n"); 
} 

La clave es proporcionar una referencia gestor de archivo abierto a CURLOPT_READDATA. Después de eso, la biblioteca core curl maneja las lecturas sin necesidad de devolución de llamada.

Cuestiones relacionadas