我使用带有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"。如果我手动更改Header为Accept:"/*"并使用浏览器的调试工具重放该请求,我将得到
401未授权的HTTP状态
我想我错过了一些很简单的东西,但我已经检查了我的SecurityConfiguration和authInterceptor而不了解缺少什么。
谁能解释一下我做错了什么?回答我自己的问题:解决方案非常非常简单,但是非常非常不令人满意:
我使用JWT身份验证的Jhipster,它依赖于HTTP-Header"授权"。EventSource不支持标头!看到
解决方案可能是使用一个支持标题的polyfill。我用这个成功地测试了它提交支持报头的事件源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);
}
})
();
由于某些原因,标题支持不再包括在主分支中,也不包括在原始的polyfill中。所以我不太确定这是正确的做法。我可能会切换到websockets。
编辑:我想我找到了一种使用标准EventSource的方法。类JWTFilter包含一种从请求参数检索访问令牌的方法。所以我可以像这样使用EventSource:
source = new EventSource('api/sse?access_token=' + AuthServerProvider.getToken());
这么简单,我有点不好意思,我之前没有看到。
基于@Jan的回答,下面是我如何修改JWTFilter的。resolveToken方法看起来像这样:
文件: java/myapp/安全/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);
};
最后,我必须修改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;
}