Why is Flutter Dart application throwing an XMLHttpRequest error when test programme and POSTMAN do not?

Issue

I have an application that was working doing http.get requests on an Ubuntu virtual machine hosting a postgres DB and an API using Hapi/node. The VM disk become corrpupted and following the rebuild, the http.get now throws an XMLHttpRequest error. However, a test programme doing the same request works fine and a test GET using POSTMAN works fine as well. I’m stumped as to why this is the case.

The code that is throwing the error is as follows (the getScores() function):

import 'package:noxo/functions.dart' as func;
import 'dart:convert';
import 'package:http/http.dart' as http;

class DataManager {
  Map<dynamic, dynamic> results = {}; // Return values from DB parameter query
  double difficulty = 5; // Level of difficulty (1- easiest to 5-hardest)

  // Address of API for data requests
  final _apiAddress = 'http://192.168.1.201:4044/';
  bool _httpAvailable = true; // Assume that http is available

  int getHumanScore() {
    func.debugPrint('httpAvailable is $_httpAvailable');
    if (_httpAvailable && results.isNotEmpty) {
      func.debugPrint('Returning HUMAN score');
      return results['humanwin'];
    } else {
      return 0;
    }
  }

  int getDrawScore() {
    if (_httpAvailable && results.isNotEmpty) {
      return results['draws'];
    } else {
      return 0;
    }
  }

  int getComputerScore() {
    if (_httpAvailable && results.isNotEmpty) {
      return results['computerwin'];
    } else {
      return 0;
    }
  }

  void setDifficulty(double value) {
    func.debugPrint('Set difficulty = $value');
    difficulty = value;
  }

  double getDifficulty() {
    func.debugPrint('Get difficulty is $difficulty');
    return difficulty;
  }

  void getScores(Function() updateScores) async {
    if (_httpAvailable) {
      // If we haven't had a previous http error - read the scores
      try {
        dynamic _parsedAddress = Uri.parse(_apiAddress);
        final response = await http.get(_parsedAddress);
        func.debugPrint(
            'doing getScores. Address = $_apiAddress.  statusCode = ${response.statusCode}');
        Map decodedResponse = jsonDecode(response.body) as Map;
        if (response.statusCode == 200) {
          func.debugPrint('getScores response: $decodedResponse');
          results = decodedResponse;
          updateScores(); // Update scores on main.dart
        } else {
          throw Exception('Unable to fetch products from the REST API');
        }
      } catch (e) {
        func.debugPrint('getScores.  Error is $e.');
        _httpAvailable = false; // Disable http checks because we had an error
        results =
            {}; // Return an empty map if the internet connection is not available
      }
    }
  }

  // Put data about scores into the database
  Future<void> putScores(String resultPath) async {
    // Only try to put the scores if http is available
    if (_httpAvailable) {
      try {
        String address = '$_apiAddress$resultPath';
        func.debugPrint('http address: $address');
        var response = await http.post(Uri.parse(address));
        func.debugPrint('http response: ${response.body.toString()}');
        // Check for sucess, throw an error if it didn't work
        if (response.statusCode == 200 ||
            response.statusCode == 201 ||
            response.statusCode == 204) {
          return;
        } else {
          throw Exception(
              'Unable to update results from the REST API.  Status Code: ' +
                  response.statusCode.toString());
        }
      } catch (e) {
        _httpAvailable = false; // Disable http requests
      }
    }
  }
}

The output when "getScores()" is called is:

getScores.  Error is XMLHttpRequest error..

The Hapi interface code is as follows:

'use strict';

const Hapi = require('@hapi/hapi');
const { options } = require('@hapi/hapi/lib/cors');
const Inert = require('@hapi/inert');
const HapiPostgresConnection = require('hapi-postgres-connection');
const path = require('path');
const debug = true;

const init = async () => {

    const server = Hapi.server({
        port: 4044,
        host: '192.168.1.201',
        //host: '0.0.0.0',
        routes: {
            cors: false
            /*
            {
                origin: ['192.168.*'],      // an array of origins or 'ignore'
                headers: ['Authorization'], // an array of strings - 'Access-Control-Allow-Headers'
                exposedHeaders: ['Accept'], // an array of exposed headers - 'Access-Control-Expose-Headers',
                additionalExposedHeaders: ['Accept'], // an array of additional exposed headers
                maxAge: 60,
                credentials: false          // boolean - 'Access-Control-Allow-Credentials'
                */
        }
    });

    await server.register([{
        plugin: HapiPostgresConnection
    }, 
    { 
        plugin: Inert
    }]);

    server.route({
        method: 'GET',
        path: '/',
        handler: async function (request, h) {
            let id = '1';
            let statement = `SELECT * FROM scores WHERE id = ${id}`;
            debugPrint(`Doing GET.  Statement = ${statement}`);
            try {
                const result = await request.pg.client.query(statement);
                debugPrint(`After GET.  Result = ${result}.  Response = ${h.response()}`);
                return h.response(result.rows[0]);
            } catch (err) {
                console.log(err);
            }
        }
    });

    server.route({
        method: 'GET',
        path: '/noxo',
        handler: (request, h) => {
            return h.file('/home/mike/programming/noxo/index.html');
        }
    });

    server.route({
        method: 'GET',
        path: '/fwebtest',
        handler: (request, h) => {
            return h.file('index.html', options [{confine: false}]);
        },
        options: {
            files: {
                relativeTo: path.join(__dirname, 'fwebtest')
            },
        },
    });

    server.route({
        method: 'GET',
        path: '/webtest',
        handler: (request, h) => {
            return h.file('./webtest/index.html', options [{confine: false}])
        }
    });

    server.route({
        method: ['PUT', 'POST'],
        path: '/',
        handler: async function (request, h) {
            let statement = 'update scores set';
            var jsonData = request.payload;
            if (jsonData.hasOwnProperty('id')) {
                delete jsonData.id;
            }
            var first = true
            for (var key of Object.keys(jsonData)) {
                if (!first) {
                    statement = statement + ",";
                }
                statement = statement + ' ' + key + ' = ' + jsonData[key];
                first = false;
            }
            statement = statement + ' where id = 1'
            debugPrint(`Doing PUT.  Statement = ${statement}`);
            try {
                const result = await request.pg.client.query(statement);
                return h.response(result.rows[0]);
            } catch (err) {
                console.log(err);
            }
        }
    });

    await server.start();
    console.log('Server running on %s', server.info.uri);
};

function buildStatement(element, index, array) {
    if (index != 'id') {
        statement = statement + index + ' = ' + value + ' ';
    }
    return this;
}

function debugPrint(value){
    if (debug == true){
        console.log(value);
    }
}

process.on('unhandledRejection', (err) => {
    console.log(err);
    process.exit(1);
});

init();

The test dart programme, which works, is as follows:

import 'dart:convert';
import 'package:http/http.dart' as http;

// Address of API for data requests
final _apiAddress = 'http://192.168.1.201:4044/';

// Global variables
bool _httpAvailable = true; // Has http conect worked?  Assume yes.
const bool debug = true; // Global bebug print variable.

void main() async {
  // Get the scores from the API
  Map scores = await _getScores();
}

// Function to get the scores using http command
Future<Map> _getScores() async {
  debugPrint('Doing GET...');
  dynamic response;
  Map decodedResponse = {};
  try {
    dynamic _parsedAddress = Uri.parse(_apiAddress);
    response = await http.get(_parsedAddress);
    decodedResponse = jsonDecode(response.body) as Map;
  } catch (e) {
    debugPrint('getScores.  Error is $e.');
    _httpAvailable = false; // Disable http checks because we had an error
    decodedResponse =
        {}; // Return an empty map if the internet connection is not available
  }
  if (response.statusCode == 200) {
    debugPrint('response.body:');
    debugPrint(response.body);
    debugPrint('GET successful... code: ' + response.statusCode.toString());
    debugPrint(' ');
    return decodedResponse;
  } else {
    throw Exception('Unable to fetch products from the REST API. Error code: ' +
        response.statusCode.toString());
  }
}

debugPrint(String message) {
  if (debug == true) {
    print(message);
  }
}

The output from this programme is:

D:\Sync\Programming\Flutter\http_get>dart run
Building package executable...
Built http_test:http_test.
Doing GET...
response.body:
{"id":1,"humanwin":3,"draws":0,"computerwin":0}
GET successful... code: 200

The POSTMAN results are:

enter image description here

I assume that this is a CORS issue from what I have read about XMLHttpRequest errors, but believe I have disabled CORS in the Hapi interface. The application class, test programme and POSTMAN test all pre-date the Ubuntu VM rebuild so the only thing that has changed is the VM. The Hapi code was backed up so that shouldn’t have changed either, although I can’t quite remember when I took the last backup.

Does anyone have any ideas about why the test programme and POSTMAN work, but my DataManager class in my application doesn’t?

Solution

In the routes definition for Hapi, cors needs to be set to true as below.

const server = Hapi.server({
    port: xxxx,
    host: 'xxx.xxx.xxx.xxx',
    routes: {
        cors: true
    }
});

Answered By – Twelve1110

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published