Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why my micro-API does not have response body?

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.

like image 221
w.k Avatar asked Sep 13 '21 14:09

w.k


People also ask

What contains API response?

An API response consists of the response body, headers, and the HTTP status code.

Can HTTP response have empty body?

1. Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, and 304 responses and any response to a HEAD request) is always terminated by the first empty line after the header fields, regardless of the entity-header fields present in the message.

What is a response interface?

The Response interface of the Fetch API represents the response to a request. You can create a new Response object using the Response() constructor, but you are more likely to encounter a Response object being returned as the result of another API operation—for example, a service worker FetchEvent.


2 Answers

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()); 
}
like image 71
I wrestled a bear once. Avatar answered Oct 20 '22 21:10

I wrestled a bear once.


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

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

like image 1
ernix Avatar answered Oct 20 '22 20:10

ernix