Ionic/Angular $http.post results in a 404, but only on an Android phone, not on an iPhone or in the browser


#1

I’m tearing my hair out here. AJAX calls using $http.post fail only on Android, not while testing in the browser, not while running on an iPhone.

app.js:

angular.module('greenroom', ['ionic', 'Controllers'])

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
   if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
   }
   if(window.StatusBar) {
      StatusBar.styleDefault();
   }
  });
})
.config(function($stateProvider, $urlRouterProvider, $ionicConfigProvider) {

   $ionicConfigProvider.views.transition('none');

   $stateProvider
      .state("main", {url: "/", templateUrl: "templates/main.html", controller: "MainController"})
      .state("login", {url: "/login", templateUrl: "templates/login.html", controller: "MainController"})
      .state("signup", {url: "/signup", templateUrl: "templates/signup.html", controller: "MainController"})
      .state("upload", {url: "/upload", templateUrl: "templates/upload.html", controller: "MainController"})
      .state("searchresults", {url: "/searchresults", templateUrl: "templates/searchresults.html", controller: "MainController"})
      .state("details", {url: "/searchresults/:id", templateUrl: "templates/details.html", controller: "DetailsController"});

   $urlRouterProvider.otherwise("/");

});

The relevant portion of controllers.js:

angular.module('Controllers',['SearchService'])
   .controller('MainController', function($scope, $location, $http, SearchService) {

      $scope.userPostal = '';
      $scope.apiEndpointAddress = 'http://real.hostname.redacted/api/dispensarySearch';
      $scope.locationSearched = SearchService.location;

      $scope.goDetails = function (item) {
         $location.path('/searchresults/' + item.id);
      };


      $scope.searchData = {
         latitude: null,
         longitude: null,
         postalCode: null
      }

      $scope.searchDispensaries = function() {

         if (this.userPostal.length==0) {

            SearchService.location = "Your current location";
            navigator.geolocation.getCurrentPosition(
               function(position) {
                  this.searchData.latitude = position.coords.latitude;
                  this.searchData.longitude= position.coords.longitude;
                  $http.post(this.apiEndpointAddress,JSON.stringify(this.searchData))
                     .success($scope.handleSuccess)
                     .error($scope.handleError);
                  alert('y');
               },
               function(error) {
                  alert('Error getting location: ' + error);
                  return;
               },
               { enableHighAccuracy: true }
            );
         } else {
            SearchService.location = this.userPostal;
            this.searchData.postalCode = this.userPostal;
            $http.post(this.apiEndpointAddress,JSON.stringify(this.searchData))
               .success(this.handleSuccess)
               .error(this.handleError);
         }
      };

      $scope.handleError = function(data, status, headers, config) {

         alert(status + ' ' + JSON.stringify(data) + ' ' + JSON.stringify(config)); // +  'Your search was unsuccessful. If you are searching based on your current location, make sure location services are enabled.');
      }

      $scope.handleSuccess = function(data, status, headers, config) {
         alert("Result: " + JSON.stringify(data));
         SearchService.searchResults = data;
         $location.path('/searchresults');
      };

   })

In the browser, or on an iOS device, handleSuccess is called. On an Android device, handleError is called with the status set to 404.

On the server side, it’s a Java-powered website using RESTeasy to create a REST API. I have a servlet filter generating these headers in response to each request:

response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.addHeader("Access-Control-Max-Age", "3600");
response.addHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");

Help, please! :frowning: Thanks in advance!


#2

I also have this in my Cordova config.xml and am using the whitelist plugin:

  <access origin="*" subdomains="true"/>

#3

You may also need to set some Content-Security-Policy meta tag in your index.html

One that handles live reload and is loose is :-

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

white list docs :-

For android I have one set like this :-

<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com; style-src 'self' 'unsafe-inline'; media-src *">

#4

At one point I had a CSP in place, but I’m not sure I did it correctly. The syntax is a little confusing. I’ll try yours. Thanks :slight_smile:


#5

Well. Now I’m getting a message “Refused to connect to [API URL] because it violates the document’s Content Security Policy.” This is definitely an improvement because I’m at least getting an error message now. :slight_smile: I used your Android CSP… now I think I just need to tweak it…


#6

That wasn’t it. I’m getting the 404 again after loosening up the CSP. Here’s the thing. I tried another REST API that already works with an internal Ionic app that I wrote… It uses HTTP GET, not POST… same 404 error I was getting with the POST to the API endpoint I actually created for this project. And what’s really weird is that other app uses $http.get and it works fine on Android AND iPhone.


#7

That didn’t fix the problem. HTTP GETs and POSTs are failing, including the GET to the API I created for that other, WORKING Ionic app. I’ve reviewed both apps as well as both server-side APIs and I am totally clueless as to why my current project isn’t working.


#8

I would suggest you replace the domain with the ip address and also enable logging on the server. Some android emulators do not work well with DNS. It might also be helpful to see if the emulator is hitting the server.

Of course don’t use 127.0.0.1 or localhost for the server in your url as well :smile:


#9

The problem is occurring on a device - an actual Android phone - not the emulator, and other than adb logcat (which isn’t showing me anything at all related to the app), I am not aware of any tools to debug my app while it’s running.


#10

Maybe it’s an file i/o related issue where you want to load a script file starting with capital where file is only lowercase. controller.js <> Controllers.js on Android file i/o.


#11

You can debug on device using chrome which debug through the Web view see this link


#12

If it was, my app wouldn’t work at all on Android. It’s only after I enter a ZIP code and click a button on the main screen, which triggers the API call, that I have a problem.


#13

The Chrome webview inspector doesn’t give me any useful information. My phone did not have a new-enough version of Chrome - but my tablet does. Nothing in the webview inspector, nothing in logcat.

I do get a message from the console. It’s just a notification that the URL was not found. I also tried removing everything from config.xml and using only a very wide-open CSP, since the whitelist plugin docs suggest using a CSP instead of configuring whitelist entries in config.xml.


#14

SOLVED!!! It was an incompatibility in Angular that I wasn’t aware of because last time I built an Ionic/Angular app, this wasn’t an issue. Here’s the fix.

  • Your index.html must have a Content-Security-Policy meta tag to make the browser AND the Cordova whitelist plugin happy (it was actually Cordova that threw an error when I temporarily removed it for testing purposes).
  • It is also necessary to include the proper directives in config.xml, to make the Cordova whitelist plugin happy.
  • You also must use the ng-csp attribute. (I wasn’t.)

Angular has some features that can break certain CSP (Content Security Policy) rules.

If you intend to implement these rules then you must tell Angular not to use these features.

This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.

The following rules affect Angular:

unsafe-eval: this rule forbids apps to use eval or Function(string) generated functions (among other things). Angular makes use of this in the $parse service to provide a 30% increase in the speed of evaluating Angular expressions.
unsafe-inline: this rule forbids apps from inject custom styles into the document. Angular makes use of this to include some CSS rules (e.g. ngCloak and ngHide). To make these directives work when a CSP rule is blocking inline styles, you must link to theangular-csp.css in your HTML manually.

If you do not provide ngCsp then Angular tries to autodetect if CSP is blocking unsafe-eval and automatically deactivates this feature in the $parse service. This autodetection, however, triggers a CSP error to be logged in the console:

https://docs.angularjs.org/api/ng/directive/ngCsp

Old, broken index.html:

<body class="greenroom" ng-app="greenroom">

Working index.html:

<body class="greenroom" ng-csp ng-app="greenroom">

#15

Hi Steve,

I´m having the same problem. Since I install some cordova plugin for google analytics, I cant $http.post in android but in iOS works fine.

I’m using this meta tag:

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

And using ng-csp as you indicates:

But still have no luck.

Any ideas? Is there something missing in my Content-Security-Policy meta tag

Any help would be appreciated

Kind regards

Francisco


#16

Yes. Your default allows everything but script-src does not.


#17

I can’t make it to work. :frowning:

I use this tag but with no luck:

<meta http-equiv="Content-Security-Policy" content="default-src 'unsafe-inline' 'unsafe-eval' data: gap: https://ssl.gstatic.com *; style-src 'self' 'unsafe-inline' *; script-src 'unsafe-inline' 'unsafe-eval' *">

Can you provide me the one you used and works. Thanks a lot


#18

Well, first off, I’d set the default-src to “self” because you don’t want to allow everything by default. Deny by default; punch specific holes in the policy only as needed.

Then you need to figure out which Google domain to put in the CSP meta tag. How are you loading the Google Analytics Javascript?


#19

It worked for me: http://stackoverflow.com/questions/30161952/ionic-angular-how-to-avoid-the-404-not-found-from-cache-after-post-reque

since I updated my Cordova and rebuild Android platform.

Edit: It works only without the meta tag (with the warning)… Seems the meta tag provided cause Facebook plugin problems.


#20

Thanks Raphael, with the white list plugin and updating cordova works. (Without the meta tag)

Thanks a lot