Spring에서 제공하는 SSE (Server Sent Events) 웹 페이지를 구현하려고합니다. 내 테스트 코드는 다음을 수행합니다.왜 SSE를 사용하려고 시도 할 때 DeferredResult가 setResult()에서 끝나는가?
브라우저는 EventSource (url)를 사용하여 서버에 연결합니다.
@RequestMapping(value="myurl", method = RequestMethod.GET, produces = "text/event-stream")
@ResponseBody
public DeferredResult<String> subscribe() throws Exception {
final DeferredResult<String> deferredResult = new DeferredResult<>();
resultList.add(deferredResult);
deferredResult.onCompletion(() -> {
logTimer.info("deferedResult "+deferredResult+" completion");
resultList.remove(deferredResult);
});
return deferredResult;
}
그래서 주로이 목록에 DeferredResult을두고 내가 완료의 경우에는 목록에서이 일을 제거 할 수 있도록 완료 콜백을 등록 : 봄은 다음과 같은 컨트롤러 코드로 요청을 받아들입니다.
이제는 타이머 메서드를 사용하여 DeferredResults를 통해 등록 된 모든 "브라우저"에 현재 타임 스탬프를 주기적으로 출력합니다.
@Scheduled(fixedRate=10000)
public void processQueues() {
Date d = new Date();
log.info("outputting to "+ LoginController.resultList.size()+ " connections");
LoginController.resultList.forEach(deferredResult -> deferredResult.setResult("data: "+d.getTime()+"\n\n"));
}
데이터는 브라우저에 전송되고 다음과 같은 클라이언트 코드가 작동합니다
var source = new EventSource('/myurl');
source.addEventListener('message', function (e) {
console.log(e.data);
$("#content").append(e.data).append("<br>");
});
이제 문제 :
DeferredResult에 완료 콜백이 모든 setResult에서 호출됩니다 () 호출 타이머 스레드에서. 따라서 어떤 이유로 setResult() 호출 후에 연결이 닫힙니다. 브라우저의 SSE가 사양에 따라 다시 연결되고 다시 동일한 작업이 다시 수행됩니다. 그래서 클라이언트 측면에서 나는 폴링 동작을 가지고 있지만, 나는 계속해서 동일한 DeferredResult에서 데이터를 반복적으로 푸시 (push) 할 수있는 열린 요청을 원합니다.
여기에 놓친 것이 있습니까? DeferredResult가 여러 결과를 보낼 수 있습니까? 나는 요청이 setResult() 후에 만 종료되는지보기 위해 타이머 스레드에서 10 초 지연을했다. 따라서 브라우저에서 요청은 타이머가 데이터를 푸시 할 때까지 계속 열려 있지만 닫힌 상태로 유지됩니다.
어떤 힌트를 주셔서 감사합니다. 한 가지 더 알아두기 : 나는 Tomcat의 모든 필터/서블릿에 async-supported를 추가했다.
Rossen에게 감사의 말씀을 전합니다. 그게 아니라고 확신하려면 바닐라 비동기 서블릿을 코딩하고 responseStream에 데이터를 반복해서 플러시하면됩니다. 물론 사실은 요청을 완료하는 AsyncContext.complete() 호출을 피할 수 있습니다. 나는 spring mvc가 setResult() 이후에 DeferredResult에서 코드 정글 어딘가에있는 것을 이해하려고 노력했다고 생각한다.어쨌든 단방향 통신 만 필요하기 때문에 WebSockets를 피하고 싶었지만 SSE는 서버 측에서 훨씬 단순하지 않았습니다 .-) – Marc
사실 DeferredResult는 단순히 AsyncContext.complete를 호출하지 않습니다. 처음에는 시도했지만 서블릿 컨테이너 외부의 HttpServletRequest에 대한 액세스는 안전하지 않으므로 이러한 접근 방식은 완전하고 투명한 Spring MVC 처리에 적합하지 않습니다. 대신 asyncContext.dispatch()를 호출합니다. 이는 컨테이너로 전달되는 것과 같으며 거기에서 평소처럼 처리를 완료합니다. –
아 좋아. 사실 나는 봄 MVC 코드에서 complete() 호출을 보지 못했고이 방법일지도 모른다고 추측했다. 어쨌든. MVC 영역에서 잘 작동합니다. 나는 Struts1 lightyears 전에 시작 했으므로 Spring MVC의 이점을 정말로 알고있다 ;-) – Marc