http://qs321.pair.com?node_id=1212636

thanos1983 has asked for the wisdom of the Perl Monks concerning the following question:

Hello fellow Monks,

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.

My question is extension of How to pass credentials through REST::Client. This time I need to POST a file through the module REST::Client.

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:

restClient.pl

#!/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/Pr +otocol/http.pm line 260. ';

Code in the main module.

ClientRest.pm

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;

If I use curl POST and it works as expected. Sample bellow:

$ curl -u user:password -H "Content-Type: multipart/form-data" -H "Acc +ept: 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" }

I was reading online on how to post a file with this module and I found How to POST attachment to Confluence page using REST API with perl?, HTTP::Request::Common and multipart/form-data, PUT a Multipart request in PERL and POST a Multipart request in Perl via LWP etc etc. All links point to the same solution as the one that I have implemented.

From the error that I am getting:

$ perl restClient.pl $VAR1 = 'Not a SCALAR reference at /usr/local/share/perl/5.22.1/LWP/Pr +otocol/http.pm line 260. ';

I checked the module following snippet is from line (257-274) where the error is coming:

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

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?

Update: I tried also to define in the file in the header as HoA (Hash Of Arrays) e.g.:

my $headers = { "Content" => ['file' => [$options{file}]], "Content-type" => 'multipart/form-data', "Authorization" => 'Basic ' . encode_base64($options{username} . ':' . $options{password +}) };

Minor note also here, normal post with dictionary (data) it works as expected. Sample of post (important parts of 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' };

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.

Update2: 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:

$VAR1 = '{"detail":"Multipart form parse error - Invalid boundary in m +ultipart: None"}';

Sample of code with the body:

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

Update3: Solution provided further down with sample of code. Hope this helps someone else also in future.

Thank you in advance for everyone time and effort.

Seeking for Perl wisdom...on the process of learning...not there...yet!