Не удается заставить события, отправленные сервером, и источник событий работать в JHipster

Я использую JHipster 3.5.0 с spring -boot и angular для создания приложения. Я хотел бы отправлять обновления из бэкэнда в пользовательский интерфейс, используя события, отправленные сервером, но я не могу заставить его работать.

Вот код моего RestController:

@RestController
@RequestMapping("/api")
public class SSEResource {
    private final List<SseEmitter> sseEmitters = new CopyOnWriteArrayList<>();

    @RequestMapping(value = "/sse", method = RequestMethod.GET)
    @Timed
    public SseEmitter getSSE() throws IOException {
        SseEmitter sseEmitter = new SseEmitter();
        this.sseEmitters.add(sseEmitter);
        sseEmitter.send("Connected");
        return sseEmitter;
    }

    @Scheduled(fixedDelay = 3000L)
    public void update() {
        this.sseEmitters.forEach(emitter -> {
            try {
                emitter.send(String.valueOf(System.currentTimeMillis()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

Мой контроллер Angaluar выглядит так:

(function () {
    'use strict';

    angular
        .module('myApp')
        .controller('SSEController', SSEController);

    SSEController.$inject = [];

    function SSEController() {
        var vm = this;            

        vm.msg = {};

        function handleCallback(msg) {
            vm.msg = msg.data;
        }

        vm.source = new EventSource('api/sse');
        vm.source.addEventListener('message', handleCallback, false);
    }
})
();

Когда я пытаюсь использовать этот код, я получаю

406 Not Acceptable HTTP status (Недопустимый статус HTTP)

из-за заголовка запроса Accept: "text / event-stream". Если я вручную изменю этот заголовок на Accept: " / *" и воспроизведу этот запрос с помощью инструментов отладки своего браузера, я получу

401 Неавторизованный статус HTTP

Я думаю, что мне не хватает чего-то довольно простого, но я уже проверил свои SecurityConfiguration и authInterceptor, не понимая, чего не хватает.

Кто-нибудь может объяснить, что я делаю не так?


person Jan    schedule 13.09.2016    source источник


Ответы (2)


Отвечая на мой собственный вопрос: решение действительно очень простое и действительно очень неудовлетворительное:

Я использую Jhipster с аутентификацией JWT, которая использует HTTP-заголовок «Авторизация». EventSource не поддерживает заголовки! См.

Решением может быть использование полифилла с поддержкой заголовков. Я успешно протестировал его с помощью этого фиксации полифилла источника событий с поддержкой заголовков

(function () {
    'use strict';

    angular
        .module('myApp')
        .controller('SSEController', SSEController);

    SSEController.$inject = ['$scope', 'AuthServerProvider'];

    function SSEController($scope, AuthServerProvider) {
        var vm = this;            

        vm.msg = {};

        var options = {
            headers : {
                Authorization : "Bearer " + AuthServerProvider.getToken()
            }
        }

        vm.source = new EventSource('api/sse', options);
        vm.source.addEventListener('message', handleCallback, false);
    }
})
();

По какой-то причине поддержка заголовков больше не включена ни в основную ветку, ни в исходный полифилл. Так что я не совсем уверен, что это правильный путь. Я, наверное, перейду на веб-сокеты.

РЕДАКТИРОВАТЬ: Я думаю, что нашел способ использовать стандартный EventSource. Класс JWTFilter содержит способ получить токен доступа из параметра запроса. Так что я могу просто использовать EventSource вот так:

source = new EventSource('api/sse?access_token=' + AuthServerProvider.getToken());

Так легко, что я немного смущаюсь, что не видел этого раньше.

person Jan    schedule 13.09.2016

Основываясь на ответе @Jan, вот как я изменил метод JWTFilter.resolveToken, чтобы он выглядел так:

Файл: java / myapp / security / jwt / JWTFilter.java.

    private String resolveToken(HttpServletRequest request){
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        // pick token as GET parameter
        bearerToken = request.getParameter("access_token");
        return bearerToken;
   }

Во внешнем интерфейсе это соответствующий код, который я использовал:

    this.eventSource = new EventSource('api/screens/sse?access_token=' + this.authServer.getToken());
    this.eventSource.addEventListener('message', evt => {
        const messageEvent = evt as MessageEvent;
        this._alarmCount = parseInt(messageEvent.data, 10);
    });
    this.eventSource.onerror = evt => {
        console.log('EventSource error' + evt.data);
    };

Наконец, мне пришлось изменить оставшийся контроллер, чтобы очистить эмиттер завершенного:

 public SseEmitter getSSE() throws IOException {
    SseEmitter sseEmitter = new SseEmitter();
    synchronized (this) {
        this.sseEmitters.add(sseEmitter);
    }
    sseEmitter.onCompletion(() -> {
        synchronized (this) {
            // clean up completed emitters
            this.sseEmitters.remove(sseEmitter);
        }
    });
    sseEmitter.send(String.valueOf(System.currentTimeMillis()));
    return sseEmitter;
}
person Cesar Mauri    schedule 04.12.2018