【问题标题】:Can't get server sent events and event source to work in JHipster无法让服务器发送的事件和事件源在 JHipster 中工作
【发布时间】:2017-01-21 01:23:15
【问题描述】:

我正在使用带有 spring-boot 和 angular 的 JHipster 3.5.0 来构建应用程序。 我想使用服务器发送的事件将更新从后端发送到 UI,但我无法让它工作。

这是我的 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();
            }
        });
    }
}

我的 Angular 控制器如下所示:

(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 不可接受的 HTTP 状态

因为请求头 Accept:"text/event-stream"。如果我手动将该标头更改为 Accept:"/*" 并使用浏览器的调试工具重播该请求,我会得到 ​​p>

401 未经授权的 HTTP 状态

我认为我遗漏了一些非常简单的东西,但我已经检查了我的 SecurityConfiguration 和 authInterceptor 却不明白遗漏了什么。

谁能解释我做错了什么?

【问题讨论】:

    标签: angularjs spring-mvc spring-boot jhipster


    【解决方案1】:

    回答我自己的问题:解决方案真的很简单,但真的很不满意:

    我正在使用带有 JWT 身份验证的 Jhipster,它依赖于 HTTP 标头“授权”。 EventSource 不支持标头! See

    解决方案可能是使用支持标头的 polyfill。我用这个成功地测试了它 Commit of eventsource polyfill with support for headers

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

    由于某些原因,头文件支持不再包含在 master 分支和原始 polyfill 中。 所以我不完全确定那是正确的方法。我可能会切换到 websockets。

    编辑: 我想我找到了一种使用标准 EventSource 的方法。 JWTFilter 类包含一种从请求参数中检索访问令牌的方法。所以我可以像这样使用 EventSource:

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

    如此简单,以至于我有点尴尬,我以前没有看到过。

    【讨论】:

      【解决方案2】:

      基于@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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-06-20
        • 1970-01-01
        • 2019-02-10
        • 2015-10-24
        • 2021-02-19
        • 1970-01-01
        • 2015-10-26
        • 2013-12-05
        相关资源
        最近更新 更多