Ionic android platform cannot communicate with backend over http

Hello,

I’m testing communication between an ionic android app and backend (Amazon EC2 server).

Here is my package.json file:

{
  "name": "test",
  "version": "0.0.1",
  "author": "Ionic Framework",
  "homepage": "https://ionicframework.com/",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/common": "~10.0.0",
    "@angular/core": "~10.0.0",
    "@angular/forms": "~10.0.0",
    "@angular/platform-browser": "~10.0.0",
    "@angular/platform-browser-dynamic": "~10.0.0",
    "@angular/router": "~10.0.0",
    "@capacitor/android": "^2.4.0",
    "@capacitor/core": "2.4.0",
    "@ionic-native/core": "^5.0.0",
    "@ionic-native/splash-screen": "^5.0.0",
    "@ionic-native/status-bar": "^5.0.0",
    "@ionic/angular": "^5.0.0",
    "rxjs": "~6.5.5",
    "tslib": "^2.0.0",
    "zone.js": "~0.10.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.1000.0",
    "@angular/cli": "~10.0.5",
    "@angular/compiler": "~10.0.0",
    "@angular/compiler-cli": "~10.0.0",
    "@angular/language-service": "~10.0.0",
    "@capacitor/cli": "2.4.0",
    "@ionic/angular-toolkit": "^2.3.0",
    "@types/jasmine": "~3.5.0",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^12.11.1",
    "codelyzer": "^6.0.0",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~5.0.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "~3.3.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "~3.9.5"
  },
  "description": "An Ionic project"
}

service to login:

import { Injectable } from '@angular/core';
import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
import {Observable, throwError} from 'rxjs';
import {catchError, shareReplay, tap} from 'rxjs/operators';


export interface Login {
  email: string;
  password: string;
  userType: string;
}

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable({
  providedIn: 'root'
})
export class LoginService {

  constructor(private http: HttpClient) { }

  login(): Observable<any> {

    // tslint:disable-next-line:prefer-const
    let loginData: Login =  {
      email : 'test@gmail.com',
      password : 'test',
      userType : 'test'
    };

    return this.http.post('http://amazon-elastic-ip:8011/authentication-api/users/login', loginData, httpOptions).pipe(
        shareReplay(),
        tap(), catchError(this.handleError));
  }

  private handleError(error: HttpErrorResponse) {
    sessionStorage.setItem('error', JSON.stringify(error));
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      console.error(
          `Backend returned code ${error.status}, ` +
          `body was: ${JSON.stringify(error.error.message)}`);
    }
    // return an observable with a user-facing error message
    return throwError(
        'Something bad happened; please try again later.');
  }
}

Home.html:

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>


  <ion-button (click)="login()">Login</ion-button>
  <h1>data</h1>
  {{data}}
  <h1>Error</h1>
  {{error}}
</ion-content>

and Home.page.ts

import { Component, OnInit } from '@angular/core';

import {LoginService} from '../services/login.service';


@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})

export class HomePage implements OnInit {

  data;
  error;

  ngOnInit() {
    console.log('Initializing HomePage');
  }

  constructor(private loginService: LoginService) {
  }


  login() {
    this.loginService.login().subscribe(data => this.data = JSON.stringify(data));
    setTimeout (() => {
      this.error = sessionStorage.getItem('error');
    }, 3000);
  }
}

Incoming rules to reach my backend server, port 8011 are: 0.0.0.0/0 and ::/0 (from anywhere)

When I debug the application with the following command:

ionic capacitor run android -l --host=MY-HOST-IP

The application works fine in my device, and I can get token from Backend after login.

But when I run it using:

 ionic capacitor run android

I have communication timeout and the following error:

error: {isTrusted: true}
isTrusted: true
headers: {normalizedNames: {}, lazyUpdate: null, headers: {}}
headers: {}
lazyUpdate: null
normalizedNames: {}
message: "Http failure response for http://amazon-elastic-ip:8011/authentication-api/users/login: 0 Unknown Error"
name: "HttpErrorResponse"
ok: false
status: 0
statusText: "Unknown Error"
url: "http://amazon-elastic-ip:8011/authentication-api/users/login"

It seems that the server doesn’t accept communication with my app, and when it tried to access the server from navigator in my device with ip address port 8011, I can get response from my REST API. Also I tried to add my device IP address to incoming rules port 8011, I got the same error.

Thanks a lot in advance for your help!

The best thing to do here is to provide an HTTPS endpoint. It’s obviously much better for security, and will likely make this problem go away.

Incidentally,

  • get rid of providedIn: 'root', because it makes your service impossible to mock out;
  • you don’t need explicit Content-Type setting, or custom headers at all here - get rid of them as they take focus away from the important parts of the code