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.
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: POST file through REST::Client
by NetWallah (Canon) on Apr 10, 2018 at 18:35 UTC | |
by thanos1983 (Parson) on Apr 11, 2018 at 08:23 UTC | |
by Anonymous Monk on Apr 10, 2018 at 18:42 UTC | |
by thanos1983 (Parson) on Apr 11, 2018 at 08:25 UTC | |
by Anonymous Monk on Apr 12, 2018 at 20:31 UTC | |
Re: POST file through REST::Client
by thanos1983 (Parson) on Apr 11, 2018 at 11:06 UTC | |
by poj (Abbot) on Apr 11, 2018 at 11:20 UTC | |
by thanos1983 (Parson) on Apr 11, 2018 at 13:33 UTC | |
Re: POST file through REST::Client
by Anonymous Monk on Apr 10, 2018 at 18:39 UTC | |
by thanos1983 (Parson) on Apr 11, 2018 at 08:35 UTC | |
by Anonymous Monk on Apr 12, 2018 at 22:36 UTC |