Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enable CORS Post Request from AngularJS to Jersey

I'm attempting to post a JSON document from an AngularJS app to a Jersey REST service. The request fails, informing me that:

XMLHttpRequest cannot load http://localhost:8080/my.rest.service/api/order/addOrder. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

Jersey REST Post Function

I have enabled (what I believe to be) the appropriate headers: Access-Control-Allow-Origin and Access-Control-Allow-Methods on the response, as seen in the method below:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/addOrder")
public Response addOrder(DBObject dbobject) {
    DB db = mongo.getDB("staffing");
    DBCollection col = db.getCollection("orders");
    col.insert(dbobject);
    ObjectId id = (ObjectId)dbobject.get("_id");
    return Response.ok()
            .entity(id)
            .header("Access-Control-Allow-Origin","*")
            .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
            .allow("OPTIONS")
            .build();
}

Angular JS Controller

I've declared the app and configured the $httpProvider with all of the settings suggested in similar Stack Overflow questions:

var staffingApp = angular.module('myApp', ['ngRoute', 'ui.bootstrap']);
myApp.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
    $httpProvider.defaults.headers.common["Accept"] = "application/json";
    $httpProvider.defaults.headers.common["Content-Type"] = "application/json";
 }]);

I've also created this controller to open a modal and handle the form:

    var modalCtrl = function($scope, $modal, $log, $http, $location) {          
    $scope.order = {
        activityTitle : null,
        anticipatedAwardDate : null,
        component : null,
        activityGroup : null,
        activityCategory : null,
        activityDescription : null
    };
    $scope.open = function () {
        var modalInstance = $modal.open({
            templateUrl: 'addOrder.html',
            windowClass: 'modal',
            controller: modalInstanceCtrl,
            resolve: {
                order : function () {
                    return $scope.order;
                    }
                }
            });
        modalInstance.result.then(function (oid) {
            $log.info("Form Submitted, headed to page...");
            $location.path("/orders/" + oid);
        }, function() { 
            $log.info("Form Cancelled")
        });
    };
};

var modalInstanceCtrl = function ($scope, $modalInstance, $log, $http, order) {
    $scope.order = order,
    $scope.ok = function () {
        $log.log('Submitting user info');
        $log.log(order);
        $log.log('And now in JSON....');
        $log.log(JSON.stringify(order));
        $http.post('http://localhost:8080/my.rest.service/api/order/addOrder', JSON.stringify(order)).success(function(data){
            $log.log("here's the data:\n");
            $log.log(data);
            $modalInstance.close(data._id.$oid)
        });
    };
    $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
    };      
};
myApp.controller('modalCtrl', modalCtrl);

To no avail, I've tried:

  • removing .allow("OPTIONS") from the response headers.
  • removing the $httpProvider configuration from the application
  • changed the $httpProvider configuration to call myApp.config(function ($httpProvider) {...}), passing the function itself rather than the array.

Get requests work with the same configuration:

@GET
@Path("/listall/")
@Produces(MediaType.APPLICATION_JSON)
public Response listAll(){
    DB db = mongo.getDB("staffing");
    DBCollection col = db.getCollection("orders");
    List<DBObject> res = col.find().limit(200).toArray();
    return Response.ok()
            .entity(res.toString())
            .header("Access-Control-Allow-Origin","*")
            .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
            .allow("OPTIONS")
            .build();       
}

with this controller that works fine:

myApp.controller('orderListCtrl', function ($scope, $http){
    $http.get('http://localhost:8080/my.rest.service/api/order/listall').success(function(data) {
        for (var i = 0; i < data.length; i++) {
            if (data[i].description.length > 200) {
                data[i].shortDesc = data[i].description.substring(0,196) + "...";
            } else {
                data[i].shortDesc = data[i].description;
            }
        };
        $scope.orders = data;
    });
});

Update #1:

I've tried the same request on a same origin basis, essentially serving the Angular application alongside the REST service from locahost:8080. This configuration worked, but required a slight change and some general clean up in my code, which I've edited above.

The Post still fails as a CORS request, however so I'm still looking for the missing piece in this configuration.

Update #2:

I've investigated the headers of the working request as they're delivered to the browser and compared them with the non-working request.

The working get request returns the following headers with its response: Working GET Request Response

The non-working post request returns headers with its response, but is missing the Access-Control-Allow-Origin header:

Non-working POST Request Response

I believe this has now become an issue of the headers being stripped off of the response prior to returning it to the client, which would then cause the browser to fail the request.

Update #3:

Submitting a test POST request to the same URL from Chrome's REST Console extension returns the appropriate response headers, as seen in the screencap below. REST Console Screencap

At this point, I can't determine what's removing the headers between Jersey and my Angular client, but I'm fairly confident that's the culprit.

like image 705
Paul Cauchon Avatar asked Sep 16 '14 04:09

Paul Cauchon


2 Answers

The problem turned out to be inadequate handling of the OPTIONS request sent in pre-flight prior to the POST request with the proper cross origin headers.

I was able to resolve the issue by downloading and implementing the CORS filter found at this page: http://software.dzhuvinov.com/cors-filter-installation.html.

If you're experiencing a similar problem, follow the instructions and test to see that your OPTIONS request is no longer failing, and is immediately followed by your successful request.

like image 198
Paul Cauchon Avatar answered Sep 23 '22 21:09

Paul Cauchon


Best way is to add Jersey Response filter which will add the CORS headers for all the methods. You don't have to change your webservices implementation.

I will explain for Jersey 2.x

1) First add a ResponseFilter as shown below

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;

public class CorsResponseFilter implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext,   ContainerResponseContext responseContext)
    throws IOException {
        responseContext.getHeaders().add("Access-Control-Allow-Origin","*");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");

  }
}

2) then in the web.xml , in the jersey servlet declaration add the below

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>
like image 45
nondescript Avatar answered Sep 24 '22 21:09

nondescript