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

A while ago I asked about Data::Dumper output which was silly error on my part that was obscuring what was really going on!

The real problem is that LinkedIn is generating an error when I attempt to post. The error is: Illegal character VCHAR='(' So, instead of struggling with LWP::Authen::OAuth2, I thought I'd write my own module using LWP::Authen::OAuth2 to do the authentication and my own method to post to LinkedIn.

But I still get the same problem. I have tried debugging all the variables I use and these are all as expected.
This is the method I have to post to LinkedIn

sub post { my ($self, $text) = @_; my $ua = $self->{'auth'}->user_agent; my $token = $self->{'auth'}->access_token; my $header = { 'Authorization:' => 'Bearer ' . $token->{'access_t +oken'}, 'X-Restli-Protocol-Version:' => '2.0.0', 'Content-type:' => 'text/json', }; my $user = $self->get_id; my $json = { 'owner' => "urn:li:person:$user", 'text' => { 'text' => $text, }, }; $ua->default_header('Content-type' => 'text/json'); return $ua->post( $self->{'auth'}->api_url_base . 'shares', Content => encode_json($json), $header, ); }

First off we get the underlaying User Agent from $self->{'auth'}. This is an instance of LWP::Authen::OAuth2 which has been authorised. I have checked that authorisation is happening correctly by calling the me endpoint which returns the authenticated user. The underlaying User Agent is an instance of LWP::UserAgent.
Next we get the user string which again I have checked to ensure it is correct - it is!

We have to call $ua->default_header('Content-type' => 'text/json'); as, for some reason, it doesn't set in $header earlier.

The call to $ua->post is going to the right place and sending the right data. The JSON payload might be a bit light to work as I have stripped it down to the bare minimum for testing. However, that would almost certainly produce a different error. When I call the method:

my $post = $linkedin->post("Some test text to post"); print Dumper $post;
I get a long JSON output back from LinkedIn. Here is the bit I think it is complaining about...
'_request' => bless( { '_content' => '{"owner":"urn:l +i:person:GKiAGefMOA","text":{"text":"Some test text to post"}}', '_uri' => bless( do{\(my $o = +'https://api.linkedin.com/v2/shares')}, 'URI::https' ), '_headers' => bless( { 'user-a +gent' => 'libwww-perl/6.49', 'hash(0 +x29bc430)' => undef, 'conten +t-type' => 'text/json', 'conten +t-length' => 77, '::std_ +case' => { + 'hash(0x29bc430)' => 'HASH(0x29bc430)', + 'if-ssl-cert-subject' => 'If-SSL-Cert-Subject' + } }, 'HTTP: +:Headers' ), '_method' => 'POST', '_uri_canonical' => $VAR1->{'_ +request'}{'_uri'} }, 'HTTP::Request' )
I do not understand where ::std_case is coming from or why it contains un expanded hash references. Is this a bug in LWP perhaps?

Any ideas on how I can debug this problem further please?

If it helps, here is the full JSON response from LinkedIn:

$VAR1 = bless( { '_msg' => 'Illegal character VCHAR=\'(\'', '_content' => '<h1>Bad Message 400</h1><pre>reason: I +llegal character VCHAR=\'(\'</pre>', '_protocol' => 'HTTP/1.1', '_headers' => bless( { 'x-li-fabric' => 'prod-lva1', 'set-cookie' => [ 'bcookie="v= +2&101cb44b-4229-4298-8a92-c5269a7fc04b"; Domain=.linkedin.com; Expire +s=Fri, 12-May-2023 04:30:20 GMT; Path=/; Secure; SameSite=None', 'li_gc=MTswO +zE2MjA3NTE5Njg7MjswMjH/a8hG8abKjZCHC2n5JOsrQCRqNMUbA1a8yBjKcfaoAw==; +Domain=.linkedin.com; Expires=Fri, 05 May 2023 19:16:27 GMT; Path=/; +Secure; SameSite=None', 'lidc="b=VB1 +7:s=V:r=V:a=V:p=V:g=3729:u=1:i=1620751968:t=1620838368:v=2:sig=AQEnd1 +nC7PL_5JIHPskD72XlQtoH5ub6"; Expires=Wed, 12 May 2021 16:52:48 GMT; d +omain=.linkedin.com; Path=/; SameSite=None; Secure' ], 'date' => 'Tue, 11 May 2021 16 +:52:48 GMT', 'client-ssl-cert-issuer' => '/ +C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA', 'client-ssl-cipher' => 'ECDHE- +RSA-AES128-GCM-SHA256', 'client-peer' => '108.174.11.8 +4:443', 'client-date' => 'Tue, 11 May +2021 16:52:48 GMT', 'content-type' => 'text/html;c +harset=iso-8859-1', 'client-ssl-socket-class' => ' +IO::Socket::SSL', 'client-response-num' => 1, 'x-li-uuid' => 'FCL5EFQRfhZA0A +HZiSsAAA==', 'content-length' => 70, '::std_case' => { 'x-li-fabric +' => 'X-Li-Fabric', 'client-resp +onse-num' => 'Client-Response-Num', 'set-cookie' + => 'Set-Cookie', 'x-li-uuid' +=> 'X-LI-UUID', 'client-ssl- +cert-issuer' => 'Client-SSL-Cert-Issuer', 'client-ssl- +cipher' => 'Client-SSL-Cipher', 'client-peer +' => 'Client-Peer', 'client-date +' => 'Client-Date', 'x-li-pop' = +> 'X-Li-Pop', 'client-ssl- +cert-subject' => 'Client-SSL-Cert-Subject', 'x-li-proto' + => 'X-LI-Proto', 'client-ssl- +socket-class' => 'Client-SSL-Socket-Class' }, 'x-li-pop' => 'prod-edc2', 'client-ssl-cert-subject' => ' +/C=US/ST=California/L=Sunnyvale/O=LinkedIn Corporation/CN=tablet.link +edin.com', 'x-li-proto' => 'http/1.1' }, 'HTTP::Headers' ), '_rc' => 400, '_request' => bless( { '_content' => '{"owner":"urn:l +i:person:GKiAGefMOA","text":{"text":"Some test text to post"}}', '_uri' => bless( do{\(my $o = +'https://api.linkedin.com/v2/shares')}, 'URI::https' ), '_headers' => bless( { 'user-a +gent' => 'libwww-perl/6.49', 'hash(0 +x29bc430)' => undef, 'conten +t-type' => 'text/json', 'conten +t-length' => 77, '::std_ +case' => { + 'hash(0x29bc430)' => 'HASH(0x29bc430)', + 'if-ssl-cert-subject' => 'If-SSL-Cert-Subject' + } }, 'HTTP: +:Headers' ), '_method' => 'POST', '_uri_canonical' => $VAR1->{'_ +request'}{'_uri'} }, 'HTTP::Request' ) }, 'HTTP::Response' );

Replies are listed 'Best First'.
Re: Brackets in LWP Post
by bliako (Abbot) on May 11, 2021 at 18:17 UTC
    Further arguments can be given to initialize the headers of the request. These are given as separate name/value pairs.
    (from LWP::UserAgent)

    You are POSTing with the wrong headers: you are giving a meaningless hashref whereas it should be dereferenced to provide name/value pairs. (that said, I can not say if all else is OK)

    return $ua->post( $self->{'auth'}->api_url_base . 'shares', Content => encode_json($json), %$header, #<<<<< );

    bw, bliako

      You are POSTing with the wrong headers:

      Fabulous!
      You are spot on...dereferencing $header makes it work as it should :) Many, many thanks.

      Bizarrely, the hashref worked to authorise a GET call to a different endpoint to fetch information about the current user. I used that to check that the headers were being passed correctly. Strange how it works for a GET but not a POST.

        Bizarrely, the hashref worked to authorise a GET call

        that's strange because LWP::UserAgent says that they all use the same notation to pass the headers: Additional headers and content options are the same as for the "get" in LWP::UserAgent method.

        anyway, cool project!

Re: Brackets in LWP Post
by choroba (Cardinal) on May 11, 2021 at 18:43 UTC
    The documentation of the post method of LWP::UserAgent shows
    my $res = $ua->post( $url, $field_name => $value, Content => $content +);

    Your code uses

    $ua->post( $self->{'auth'}->api_url_base . 'shares', Content => encode_json($json), $header, );

    So, dereference the header (I also tried to moving it before Content, but it doesn't seem to matter). This should remove the hash reference from the request.

    $ua->post( $self->{'auth'}->api_url_base . 'shares', %$header, Content => encode_json($json) );

    The ::std_case field just handles standard lowercasing of header keys, nothing to worry about.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Fantastic thanks :)
      That's moved me forwards significantly...

Re: Brackets in LWP Post
by 1nickt (Canon) on May 11, 2021 at 17:36 UTC

    Hi,

    What is the value $self->{'auth'}->api_url_base? It looks like an object, should it not be a string?


    The way forward always starts with a minimal test.
      What is the value $self->{'auth'}->api_url_base?

      $self->{'auth'} is the authenticated instance of LWP::Authen::OAuth2. api_url_base is a method of that module that just returns a string with the base path of the API. In this case, the string is https://api.linkedin.com/v2/.

      documentation