perlquestion
thanos1983
<p>Hello fellow Monks,</p>
<p>I got again stack on something possibly (minor) problem that I can not over come and I need to ask you guys in case that someone know more about it.</p>
<p>My question is extension of [id://1212413|How to pass credentials through REST::Client]. This time I need to POST a file through the module [http://search.cpan.org/~kkane/REST-Client/lib/REST/Client.pm|REST::Client].</p>
<p>The scenario is more or less the same, I have a server listening on the background and I use a small module to send a file. Sample of code:</p>
<p><strong>restClient.pl</strong></p>
<readmore>
<code>
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use MyClientRest::ClientRest;
my $host = "http://127.0.0.1:8000";
# instatiate class
my $object = new ClientRest( $host );
my $username = "user";
my $password = "password";
my $file = 'test.txt';
my $upload = '/upload/';
my %optionsFile = ( "url" => $upload,
"file" => $file,
"username" => $username,
"password" => $password );
my $postFile = $object->postSnippetsFile( %optionsFile );
print Dumper $postFile;
__END__
$ perl restClient.pl
$VAR1 = 'Not a SCALAR reference at /usr/local/share/perl/5.22.1/LWP/Protocol/http.pm line 260.
';
</code>
</readmore>
<p>Code in the main module.</p>
<p><strong>ClientRest.pm</strong></p>
<readmore>
<code>
package ClientRest;
use JSON;
use Carp;
use strict;
use warnings;
use MIME::Base64;
use Data::Dumper;
use version;
our $VERSION;
$VERSION = qv('0.0.1');
use REST::Client;
sub new {
my $class = shift;
my $self = {
_host => shift,
};
_parameterValidation($self);
# instatiate Rest::Client and create constructor
my $client = REST::Client->new({
host => $self->{_host},
timeout => 10, });
$self->{_client} = $client;
bless $self, $class;
return $self;
}
sub _parameterValidation {
my( $self ) = @_;
croak "Invalid host syntax: sample 'http://<host>:<port>' "
unless ( $self->{_host} =~ /^http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}$/ );
}
sub postSnippetsFile {
my ( $self, %options ) = @_;
my $headers = { "Content" => {'file' => $options{file}},
"Content-type" => 'multipart/form-data',
"Authorization" => 'Basic ' .
encode_base64($options{username} . ':' . $options{password}) };
$self->{_client}->POST( $options{url},
$headers );
return $self->{_client}->responseContent();
}
1;
</code>
</readmore>
<p>If I use [https://linux.die.net/man/1/curl|curl] POST and it works as expected. Sample bellow:</p>
<readmore>
<code>
$ curl -u user:password -H "Content-Type: multipart/form-data" -H "Accept: application/json" -H "Expect:" -F file=@test.txt -X POST http://127.0.0.1:8000/upload/ | jq '.'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 455 100 249 100 206 2547 2107 --:--:-- --:--:-- --:--:-- 2567
{
"url": "http://127.0.0.1:8000/snippets/2/",
"id": 2,
"highlight": "http://127.0.0.1:8000/snippets/2/highlight/",
"owner": "user",
"title": "Default Title",
"code": "Test POST file Perl",
"linenos": false,
"language": "perl",
"style": "emacs"
}
</code>
</readmore>
<p>I was reading online on how to post a file with this module and I found [https://community.atlassian.com/t5/Answers-Developer-Questions/How-to-POST-attachment-to-Confluence-page-using-REST-API-with/qaq-p/558306|How to POST attachment to Confluence page using REST API with perl?], [id://248748|HTTP::Request::Common and multipart/form-data], [id://1122236|PUT a Multipart request in PERL] and [https://ullu.wordpress.com/2010/08/19/post-a-multipart-request-in-perl-via-lwp/|POST a Multipart request in Perl via LWP] etc etc. All links point to the same solution as the one that I have implemented.</p>
<p>From the error that I am getting:</p>
<readmore>
<code>
$ perl restClient.pl
$VAR1 = 'Not a SCALAR reference at /usr/local/share/perl/5.22.1/LWP/Protocol/http.pm line 260.
';
</code>
</readmore>
<p>I checked the module following snippet is from line (257-274) where the error is coming:</p>
<readmore>
<code>
else {
# Set (or override) Content-Length header
my $clen = $request_headers->header('Content-Length');
if (defined($$content_ref) && length($$content_ref)) {
$has_content = length($$content_ref);
if (!defined($clen) || $clen ne $has_content) {
if (defined $clen) {
warn "Content-Length header value was wrong, fixed";
hlist_remove(\@h, 'Content-Length');
}
push(@h, 'Content-Length' => $has_content);
}
}
elsif ($clen) {
warn "Content-Length set when there is no content, fixed";
hlist_remove(\@h, 'Content-Length');
}
}
</code>
</readmore>
<p>I can understand that the header is producing the problem but I am not sure where I am going wrong. Can anyone else spot the problem?</p>
<p><strong>Update: </strong>I tried also to define in the file in the header as HoA (Hash Of Arrays) e.g.:</p>
<readmore>
<code>
my $headers = { "Content" => ['file' => [$options{file}]],
"Content-type" => 'multipart/form-data',
"Authorization" => 'Basic ' .
encode_base64($options{username} . ':' . $options{password}) };
</code>
</readmore>
<p>Minor note also here, normal post with dictionary (data) it works as expected. Sample of post (important parts of code):</p>
<readmore>
<code>
# module part
sub postSnippets {
my ( $self, %options ) = @_;
my $headers = { "Content-type" => 'application/json; charset=UTF-8',
"Authorization" => 'Basic ' .
encode_base64($options{username} . ':' . $options{password}) };
$self->{_client}->POST( $options{url},
encode_json($options{hashRef}),
$headers );
return decode_json $self->{_client}->responseContent();
}
# script part
my $hashRef = { "title" => "Test Title",
"code" => "print \"Test POST Request\"",
"linenos" => "false",
"language" => "perl",
"style" => "emacs" };
my %options = ( "url" => $url,
"hashRef" => $hashRef,
"username" => $username,
"password" => $password );
my $post = $object->postSnippets( %options );
print Dumper $post;
# output
$ perl restClient.pl
$VAR1 = {
'linenos' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
'id' => 2,
'language' => 'perl',
'code' => 'print "Test POST Request"',
'title' => 'Test Title',
'style' => 'emacs',
'highlight' => 'http://127.0.0.1:8000/snippets/2/highlight/',
'url' => 'http://127.0.0.1:8000/snippets/2/',
'owner' => 'user'
};
</code>
</readmore>
<p>The models that process the file upload and the simple code post are different but they should work with the a bit different header as the sample of code posted above.</p>
<p><strong>Update2: </strong>After a lot of experimentation and research on the web I found that I a missing the body, I have added the body but now something else is missing. I get the following error:</p>
<readmore>
<code>
$VAR1 = '{"detail":"Multipart form parse error - Invalid boundary in multipart: None"}';
</code>
</readmore>
<p>Sample of code with the body:</p>
<readmore>
<code>
sub postSnippetsFile {
my ( $self, %options ) = @_;
my $headers = { "Content-type" => 'multipart/form-data',
"Content" => [ file => [$options{file}]],
"Authorization" => 'Basic '.
encode_base64($options{username} . ':' . $options{password}), };
$self->{_client}->POST( $options{url},
encode_json({file => $options{file}}),
$headers );
return $self->{_client}->responseContent();
}
</code>
</readmore>
<p><strong>Update3: </strong>Solution provided further down with sample of code. Hope this helps someone else also in future.</p>
<p>Thank you in advance for everyone time and effort.</p>
<div class="pmsig"><div class="pmsig-1069339">
Seeking for Perl wisdom...on the process of learning...not there...yet!
</div></div>