简体   繁体   English

无法获取服务器发送的事件和事件源以在JHipster中工作

[英]Can't get server sent events and event source to work in JHipster

I'm using JHipster 3.5.0 with spring-boot and angular to build an application. 我正在将JHipster 3.5.0与spring-boot和angular一起使用来构建应用程序。 I would like to send updates from the backend to the UI using server sent events, but I can't get it to work. 我想使用服务器发送的事件将更新从后端发送到UI,但是无法正常工作。

Here is the code of my RestController: 这是我的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();
            }
        });
    }
}

My Angaluar controller looks like this: 我的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);
    }
})
();

When I try to use that code I receive a 当我尝试使用该代码时,会收到一个

406 Not Acceptable HTTP status 406不可接受的HTTP状态

because of the request header Accept:"text/event-stream" . 由于请求标头Accept:“ text / event-stream” If I manually change that Header to Accept:" /*" and replay that request using the debug tools of my browser I get 如果我手动将标头更改为接受:“ / *”,然后使用浏览器的调试工具重播该请求,则会得到

401 Unauthorized HTTP status 401未经授权的HTTP状态

I think I'm missing something quite simple, but I allready checked my SecurityConfiguration and authInterceptor without understanding what is missing. 我想我缺少了一些非常简单的东西,但是我已经检查了我的SecurityConfiguration和authInterceptor而不了解丢失了什么。

Can anybody explain what I'm doing wrong? 有人可以解释我在做什么错吗?

To anwser my own question: The solution is really really easy und really really unsatisfing: 要回答我自己的问题:解决方案确实非常简单,也确实不满意:

I'm using Jhipster with JWT authentication, which relies on a HTTP-Header "Authorization". 我正在将Jhipster与JWT身份验证结合使用,该身份验证依赖于HTTP标头“身份验证”。 EventSource don't support headers! EventSource不支持标题! See 看到

The solution could be using a polyfill with support for headers. 解决方案可能是使用支持标题的polyfill。 I successfully tested it with this Commit of eventsource polyfill with support for headers 我通过此事件源polyfill的提交成功地测试了它并支持标题

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

For some reason the header support is no longer included in the master branch nor in the original polyfill. 由于某些原因,标头支持不再包含在master分支或原始polyfill中。 So I'm not entirly sure thats the right way to go. 因此,我不确定这是否是正确的方法。 I will probably switch to websockets. 我可能会切换到websockets。

EDIT: I think I found a way to use standard EventSource. 编辑:我想我找到了一种使用标准EventSource的方法。 The class JWTFilter contains a way to retrieve the access token from a request parameter. JWTFilter类包含一种从请求参数中检索访问令牌的方法。 So I can just use the EventSource like this: 所以我可以像这样使用EventSource:

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

So easy that I'm kind of embarrassed that I didn't see that before. 如此轻松,以至于让我有些尴尬,以至于我以前从未见过。

Building upon @Jan answer, here is how I modified the JWTFilter.resolveToken method to look like this: 在@Jan答案的基础上,这是我如何修改JWTFilter.resolveToken方法以使其看起来像这样:

File : java/myapp/security/jwt/JWTFilter.java 文件 :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;
   }

In the front-end this is the relevant code I used: 在前端,这是我使用的相关代码:

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

Finally, I had to modify the rest controller to cleanup completed emitter: 最后,我不得不修改rest控制器以清理完成的发射器:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM