5

For my little Javascript app I wrote serverside API function with CGI.

I made it very simple, and full example script looks like that:

#!/usr/bin/env perl

use strict; use warnings; use 5.014; 

use CGI;
use JSON;
use Data::Dumper;

my $q = new CGI;
my %p = $q->Vars;

_api_response();

sub _api_response {
  my ( $error ) = @_;
  my $res;

  my $status = 200;
  my $type = 'application/json';
  my $charset = 'utf-8';

  if ( $error ) {
    $status = 500;
    $res->{data} = {
      status => 500,
    };
    $res->{error} = {
        error => 'failure',
        message => $error,
        detail => Dumper \%p,
    };
  } else {
    $res->{data} = {
      status => 200,
    };
  }

  print $q->header( 
    -status   => $status, 
    -type     => $type,
    -charset  => $charset,
  );

  my $body = encode_json( $res );
  print $body;
}

When I call it from JS script with fetch, it gets no response body. If I checked from Developers Tools/Network, it has also no response body. If I enter the same URL into browser, it shows JSON body. If I use curl as

curl -v 'https://example.com/my_api?api=1;test=2;id=32'

response seems have also correct body:

< HTTP/2 200 
< date: Mon, 13 Sep 2021 14:04:42 GMT
< server: Apache/2.4.25 (Debian)
< set-cookie: example=80b7b276.5cbe0f250c6c7; path=/; expires=Thu, 08-Sep-22 14:04:42 GMT
< cache-control: max-age=0, no-store
< content-type: application/json; charset=utf-8
< 
* Connection #0 to host example.com left intact
{"data":{"status":200}}

Why fetch does not see it as a body?

For sake of completeness, I include JS part also:

async function saveData(url = '', data = {}) {
  const response = await fetch(url, {
    method: 'GET', 
    mode: 'no-cors', 
    cache: 'no-cache', 
    credentials: 'omit',
    headers: {
      'Content-Type': 'application/json'
    },
    redirect: 'follow', 
    referrerPolicy: 'no-referrer', 
  });
  console.log(response); // body is null
  return response.json(); 
}

Using the function as:

saveData('https://example.com/my_api?api=1;test=2;id=32', { answer: 42 })
  .then(data => {
    console.log(data);
  })
  .catch( error => {
    console.error( error );
  });

On console I see error:

SyntaxError: Unexpected end of input

One possible reason for this error is empty JSON string.

w.k
  • 8,218
  • 4
  • 32
  • 55
  • Why do you have a `content-type` header in `fetch`? This header specifies the content type of the request body, but there is no request body since it is a GET request. For signaling to the server what kind of content you accept the `Accept` header should be used. Note also that you don't send this header in your `curl` command, so this header might actually be the problem. – Steffen Ullrich Sep 13 '21 at 14:49
  • @SteffenUllrich `content-type` was clearly unneccessary, but omitting it made no change. – w.k Sep 13 '21 at 15:10
  • Have you done any debugging on the Perl side? What do you see in your server log? – simbabque Sep 13 '21 at 16:51
  • You did not demonstrate how `my_api` calls `_api_response`. – Polar Bear Sep 13 '21 at 16:55
  • @PolarBear I modified my example to fully functional script, with calling the function. – w.k Sep 13 '21 at 18:58
  • @simbabque Server logs show accessing, but nothing out of order for my eye... I don't see any significant difference in %ENV-s in different calls . – w.k Sep 13 '21 at 19:21
  • 1
    You call `_api_response()` without any arguments. `_api_response(@args)` expects some arguments, if arguments are not provided then you end up with `print $q->header(...)` and `print $body` where body is `json { status => 200 }` (exactly what request from web browser receives). It looks like your problem is in JavaScript, for debug purpose add in Perl code verification block which dumps received data into a file for a study what could be a cause of the issue. – Polar Bear Sep 13 '21 at 19:48
  • 1
    I can reproduce your problem with the code you've shown. – simbabque Sep 14 '21 at 09:01
  • Check your js again, error does not look like runtime one. Ie. https://stackoverflow.com/a/3983103/223226 – mpapec Sep 19 '21 at 13:35
  • Also check the network tab in the browser console. – mpapec Sep 19 '21 at 13:40
  • @mpapec all I see on console is `test__bootstrap_modal.html:143 SyntaxError: Unexpected end of input at saveData (test__bootstrap_modal.html:196)`. And as I mentioned in OP, the Network tab is on same tune with `fetch`: there is no response body. I tried also with Postman and it shows correct JSON body as response – w.k Sep 19 '21 at 16:03
  • Does error appear immediately or on fetch execution? – mpapec Sep 19 '21 at 16:28
  • @mpapec on fetch execution – w.k Sep 19 '21 at 17:21

2 Answers2

3

I was able to reproduce your problem, and then I was able to fix it.

It was a CORS issue. You need to enable CORS on both the front and the back end.

On the front end you need to set the content security policy with a meta tag in your page's <head>:

<meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost">

(Don't forget to change localhost to whatever your real domain is.)

On the back you need to add the CORs header:

  print $q->header( 
    -status   => $status, 
    -type     => $type,
    -charset  => $charset,
    -access_control_allow_origin => '*', # <-- add this line
  );

As a side note, none of the settings you're passing into fetch are necessary. And since you're awaiting the response and then returning another promise anyway, there is really no reason for that to even be a an async function.

Until you're ready to do something with the unused data argument, the following code will suffice:

function saveData(url = '', data = {}) {
    return fetch(url).then(response=>response.json()); 
}
I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116
1

You have to await for response.json() too.

Try return await response.json();, instead of return response.json();

ernix
  • 3,442
  • 1
  • 17
  • 23
  • I don't follow your solution, exactly. `async` function should return `Promise` and `saveData` does. I don't see reason to use `await` here (and it does not change the result). – w.k Sep 14 '21 at 23:23
  • it's a little wierd that they're awaiting a promise and then returning another promise anyway, but that isn't the issue. if you awaited the response then the code will break later when they call `then` on a non-promise. – I wrestled a bear once. Sep 20 '21 at 20:49