如何使用curl上传(post)大型文件

5
我使用WWW::Curl上传文件:
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); 

这里没有什么特别的,代码可以正常工作。

我想上传大文件,但不想将所有内容预加载到内存中。至少我听说libcurl正在做这件事。

CURLOPT_READFUNCTION接受回调函数,返回内容的部分。这意味着我无法使用WWW::Curl::Form设置POST参数,而是必须通过此回调函数返回整个内容。是这样吗?

我认为代码可能如下所示:

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} ? '@'.@{$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;
});

CURLOPT_READFUNCTION回调函数需要返回哪些数据?$params和文件内容?它们应该是哪种格式?

我真的需要自己创建由CURLOPT_READFUNCTION返回的数据吗?还是有简单的方法可以创建正确格式的数据?

谢谢。


你是否决定使用WWW::Curl?如果你可以切换的话,我认为使用LWP会更容易些。 - wes
LWP或者更好的选择是WWW::Mechanize。 - Gilles Quénot
我知道这个答案与你的代码没有直接关系,但是我花了很长时间使用WWW::Mechanize来解决类似问题,最终发现我们的管理员将Web服务器上的MaxPostSize设置为某个任意限制。 - AWT
我已经使用LWP完成了它,但比起libcurl要慢得多。我将检查WWW::Mechanize。谢谢。 - toktok
2个回答

4

需要翻译的内容:

测试16formpost.t与IT技术有关。正如您所看到的,它完全被禁用。这个事实和我对回调函数各种返回值的失败尝试让我相信Perl绑定中的CURLOPT_READFUNCTION特性已经失效。

我必须通过这个回调返回整个内容。是这样吗?

不是的,您可以适合分块编码地分多次提供请求体,回调将被必要地调用多次,根据在CURLOPT_INFILESIZE中设置的限制。

CURLOPT_READFUNCTION回调必须返回哪些数据?

HTTP请求正文。由于您正在上传文件,这意味着Content-Type multipart/form-data。以下是使用HTTP::Message的示例。CURLOPT_HTTPPOST是构建此格式的另一种方法。

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;

谢谢。非常有用的信息。HTTP::Message 的问题在于它会将整个内容加载到内存中。我只有有限的内存(64MB),这意味着请求必须更小。今天我会尝试创建一个回调函数,以分块返回内容,而不是缓冲整个内容。 - toktok
刚刚收到 WWW::Curl 维护者的消息,关于 CURLOPT_READFUNCTION 特性:“是的,看起来相当糟糕。我计划在未来几周对 WWW::Curl 进行全面改进,可能也会修复这个问题。” - daxim
你为什么认为它是坏的?我对于READFUNCTION没有任何问题,它的行为与curl主页上所解释的完全一致。“对我而言它是工作正常的”;-) - toktok
确认一下:当您运行它时,我答案中的程序会发送一个非空请求主体。 - daxim
READFUNCTION必须在没有更多字节可返回时返回0。我曾经/现在遇到了“Transfer-Encoding: chunked”的问题,因为服务器响应“缺少Content-Length”,尽管它是分块传输。我删除了该头行并解决了问题。 - toktok

3

CURLOPT_READFUNCTION回调仅用于分块传输编码。它可能会起作用,但我无法做到这一点,并发现即使这样做也不必要。

我的使用案例是将数据上传到AWS,其中不能将数据作为多部分表单数据上传。相反,它是数据的直接POST。但是,它确实需要您知道要发送到服务器的数据量。以下方法对我有效:

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");
}

关键在于提供一个打开的文件句柄引用给CURLOPT_READDATA。之后,curl核心库将处理来自该引用的读取,而不需要回调函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接