2014-09-17 2 views
2

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를 추가했다.

답변

2

실제로 DeferredResult는 한 번만 설정할 수 있습니다 (setResult가 부울을 반환 함을 알 수 있음). Spring MVC 처리 옵션의 전체 범위에서 처리를 완료합니다. 즉, 비동기로 생성 된 반환 값을 제외하고 Spring MVC 요청 중에 일어나는 일이 다소 동일하게 유지된다는 것을 의미합니다.

SSE에 필요한 것은보다 집중된 것입니다. 즉, HttpMessageConverter를 사용하여 응답에 각 값을 씁니다. 나는 그 티켓을 만들었습니다. https://jira.spring.io/browse/SPR-12212.

스프링의 SockJS 지원에는 쿠키가있는 도메인 간 요청 (IE의 경우 중요)과 같은 몇 가지 추가 기능을 처리하는 SSE 전송 기능이 있습니다. 또한 WebSocket API 및 WebSocket 스타일의 메시징 (클라이언트 또는 서버 측에서 WebSocket을 사용할 수없는 경우에도)을 사용하여 HTTP 긴 폴링의 세부 사항을 완전히 추상화합니다.

해결 방법으로 HttpMessageConverter를 사용하여 서블릿 응답에 직접 쓸 수도 있습니다.

+0

Rossen에게 감사의 말씀을 전합니다. 그게 아니라고 확신하려면 바닐라 비동기 서블릿을 코딩하고 responseStream에 데이터를 반복해서 플러시하면됩니다. 물론 사실은 요청을 완료하는 AsyncContext.complete() 호출을 피할 수 있습니다. 나는 spring mvc가 setResult() 이후에 DeferredResult에서 코드 정글 어딘가에있는 것을 이해하려고 노력했다고 생각한다.어쨌든 단방향 통신 만 필요하기 때문에 WebSockets를 피하고 싶었지만 SSE는 서버 측에서 훨씬 단순하지 않았습니다 .-) – Marc

+1

사실 DeferredResult는 단순히 AsyncContext.complete를 호출하지 않습니다. 처음에는 시도했지만 서블릿 컨테이너 외부의 HttpServletRequest에 대한 액세스는 안전하지 않으므로 이러한 접근 방식은 완전하고 투명한 Spring MVC 처리에 적합하지 않습니다. 대신 asyncContext.dispatch()를 호출합니다. 이는 컨테이너로 전달되는 것과 같으며 거기에서 평소처럼 처리를 완료합니다. –

+0

아 좋아. 사실 나는 봄 MVC 코드에서 complete() 호출을 보지 못했고이 방법일지도 모른다고 추측했다. 어쨌든. MVC 영역에서 잘 작동합니다. 나는 Struts1 lightyears 전에 시작 했으므로 Spring MVC의 이점을 정말로 알고있다 ;-) – Marc

관련 문제