Включить запрос CORS от AngularJS до Джерси

Я пытаюсь опубликовать документ JSON из приложения AngularJS в службу REST Джерси. Запрос не сработал, сообщив мне, что:

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.

Функция отправки REST в Джерси

Я включил (что я считаю) соответствующие заголовки: Access-Control-Allow-Origin и Access-Control-Allow-Methods в ответе, как показано ниже:

@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-контроллер

Я объявил приложение и настроил $httpProvider со всеми настройками, предложенными в похожих вопросах:

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";
 }]);

Я также создал этот контроллер, чтобы открыть modal и обработать форму:

    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 the data:\n");
            $log.log(data);
            $modalInstance.close(data._id.$oid)
        });
    };
    $scope.cancel = function () {
        $modalInstance.dismiss('cancel');
    };      
};
myApp.controller('modalCtrl', modalCtrl);

Безрезультатно, я пробовал:

  • удаление .allow("OPTIONS") из заголовков ответов.
  • удаление конфигурации $httpProvider из приложения
  • изменил конфигурацию $httpProvider, чтобы вызвать myApp.config(function ($ httpProvider) {...}), передав сама функцию, а не массив.

Получить запросы с той же конфигурацией:

@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();       
}

с этим контроллером, который отлично работает:

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;
    });
});

Обновление # 1:

Я пробовал один и тот же запрос на основе того же происхождения, по существу обслуживая приложение Angular рядом с службой REST от locahost: 8080. Эта конфигурация работала, но требовала небольшого изменения и некоторой общей очистки в моем коде, которую я редактировал выше.

Сообщение по-прежнему не работает как запрос CORS, однако я все еще ищу недостающую часть в этой конфигурации.

Обновление # 2:

Я исследовал заголовки рабочего запроса по мере их доставки в браузер и сравнил их с нерабочим запросом.

Рабочий запрос получения возвращает следующие заголовки с его ответом: Working GET Request Response

Нерабочий почтовый запрос возвращает заголовки с ответом, но отсутствует заголовок Access-Control-Allow-Origin:

Non-working POST Request Response

Я считаю, что теперь это стало проблемой, когда заголовки были отстранены от ответа до того, как они вернули его клиенту, что приведет к сбою запроса браузера.

Обновление № 3:

Отправка тестового запроса POST на тот же URL-адрес из консоли Chrome REST Console возвращает соответствующие заголовки ответов, как показано в скринкапе ниже. REST Console Screencap

На данный момент я не могу определить, как удалить заголовки между Джерси и моим клиентом Angular, но я уверен, что виновник.

Ответ 1

Проблема оказалась неадекватной обработкой запроса OPTIONS, отправленного перед полетом до запроса POST с соответствующими заголовками перекрестного происхождения.

Я смог решить проблему, загрузив и внедряя фильтр CORS, найденный на этой странице: http://software.dzhuvinov.com/cors-filter-installation.html.

Если у вас возникла аналогичная проблема, следуйте инструкциям и протестируйте, чтобы убедиться, что ваш запрос OPTIONS больше не работает, и сразу же следует ваш успешный запрос.

Ответ 2

Лучший способ - добавить фильтр Джерси-ответ, который добавит заголовки CORS для всех методов. Вам не нужно менять реализацию веб-сервисов.

Я объясню для Jersey 2.x

1) Сначала добавьте ResponseFilter, как показано ниже.

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), то в web.xml в объявлении сервлета джерси добавьте ниже

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>

Ответ 3

Я столкнулся с аналогичной ошибкой CORS при вызове моей Restful service (реализованной в java-Jersey) из angularjs. Чтобы исправить это, я добавил Access-Control-Allow-Origin: * в заголовке ответа. Я добавил ниже:

response.addHeader("Access-Control-Allow-Origin", "*"); 

Для получения дополнительной информации вы можете проверить - http://enable-cors.org/server.html

Ошибка CORS возникает, когда ваш код angularjs (веб-проект) и код webserivce (проект на стороне сервера) находятся на разных IP-адресах и портах.

Ваша реализация webservice выглядит правильно. Поэтому просто проверьте, попробуйте запустить их на localhost на одном и том же порту (например, 8080). Он должен работать там, если весь код верен.

Чтобы запустить их отдельно, попробуйте добавить Access-Control-Allow-Origin: * в реализацию webservice, как показано выше.

Надеюсь, что это поможет.

Ответ 4

На самом деле у вас есть другое решение, для которого не требуется фильтр. Добавление заголовков Access-Control-Allow-* в запрос GET недостаточно, вам нужно создать конечную точку OPTIONS, чтобы браузеры могли выполнять предполетный запрос, т.е.:

@OPTIONS
public Response corsMyResource(@HeaderParam("Access-Control-Request-Headers") String requestH) {
    ResponseBuilder rb = Response.ok();

    return buildResponse(rb, requestH);
}

см. https://kdecherf.com/blog/2011/06/19/java-jersey-a-cors-compliant-rest-api/ для справки.