끄적끄적 코딩일지

[Spring] SSeEitter 사용하기 본문

Spring

[Spring] SSeEitter 사용하기

BaekGyuHyeon 2022. 6. 9. 18:06

SSE(Server Send Event)에 대한 개념은 아래글을 참고하자

 

Web에서 실시간 데이터를 표시하기

Http 통신은 한번 통신이 이루어지면 연결이 완전히 끊긴다. 때문에 일반적으로는 Server에서 일어나는 변화를 Client에 알릴 방법이 없다. 그렇다고 단순히 Request를 지속적으로 보낸다면 그만큼 서

blablacoding.tistory.com

 

SSE는 Http 기반의 Streaming을 활용한 기술이므로 Spring-web 라이브러리가 필요하다.


SSE를 사용하여 데이터 베이스 데이터를 실시간으로 받아오도록 해 보자

Step 1. Controller에서 SSE 만들기

@Controller
public class SseController{
    // Client의 현제 연결 목록
    private Set<SseEmitter> emitterSet = new CopyOnWriteArraySet<>();
    
    @Autowired
    private MemberService service;

    // 새로운 Sse 만들기
    @GetMapping("/createSse")
    public SseEmitter createEmmit(){
        SseEmitter emit = new SseEmitter(60*60*1000L); // 입력값은 time out 시간(ms)이다.
        
        //emitterSet에 만든 연결을 추가한다. 
        emitterSet.add(emitter);
        
        // timeout이 발생하거나 완료되면 연결 목록에서 제거한다.
        emitter.onTimeout(()->emitterSet.remove(emitter));
        emitter.onCompletion(()->emitterSet.remove(emitter));
        return emitter;
    }
    
    // Member정보 추가 API
    @PostMapping("/member")
    @ResponseBody
    public Member saveMember(@RequestBody Member member){
        return service.save(member);
    }
    
    // Member 정보 수정 API
    @PatchMapping("/member")
    @ResponseBody
    public Member saveMember(@RequestBody Member member){
        return service.save(member);
    }
    
    // 비동기처리
    // 이벤트 감지
    // 이벤트로 Member 객체가 입력되면 해당 이벤트 리스너에서 처리한다.
    @Async
    @EventListener
    public void catchEvent(Member member){
       // 전송 오류가 나면 연결이 끊긴 Sse으로 간주하고 목록에서 제거한다.
       List<SseEmitter> deadEmit= new ArrayList<>();
       
       emitterSet.forEach(e->{
            try{
                // sse라는 이름의 이벤트로 Member객체를 JSON타입으로 전송한다.
                e.send(SseEmitter.event().name("sse").data(member,MediaType.APPLICATION_JSON));
            }catch (Exception ex){
                deadEmit.add(e);
            }
        });
        emitterSet.removeAll(deadEmit);
    }
}

Step 2. Service 만들기

위에서 member가 service.save를 통해 database에 생성될때 Member을 Event으로 등록해주어야 한다.

@Service
@RequiredArgsConstructor
@Getter
public class MemberService {
    // JpaRepository를 상속한 Member Repository
    private final MemberRepo repo;
    
    // Spring에서 Event를 발생시켜주는 Bean
    // Spring 실행할때 기본적으로 bean으로 등록하기때문에 따로 만들지 않아도 된다.
    private final ApplicationEventPublisher eventPublisher;

    public Member save(Member member){
    
        // database에 member 저장
        Member m = repo.save(member);
        
        // ApplicationEventPublisher에 저장한 Member를 이벤트으로 발생
        eventPublisher.publishEvent(m);
        return m;
    }

}

Step 3. JavaScript상에서 SSE 연결하기

// 입력한 Url으로 Sse를 만들고 이벤트를 기다린다.
const eventSource = new EventSource('/createSse');

// addEventListener으로 이벤트 처리하기
eventSource.addEventListener("sse",(event)=>{
    // "sse"라는 이벤트 발생시 처리 로직
    // 이번 예제에서는 테이블에 새로운 row를 추가하거나 수정을 하도록 함
    console.log(event);
    // data으로 설정한 값 받기
    const data = JSON.parse(event.data);
    let trs = document.getElementById(data.id);
    if(trs === null){
        trs = document.getElementById('origin').cloneNode(true);
        trs.setAttribute("id",data.id);
        trs.style.display="";
        document.getElementsByTagName('tbody')[0].append(trs);
        trs.getElementsByClassName("idx")[0].innerText= document.getElementsByTagName('tbody')[0].children.length-1;
    }
    trs.getElementsByClassName("member-email")[0].innerText=data.email;
    trs.getElementsByClassName("member-name")[0].innerText=data.name;
    trs.getElementsByClassName("member-profile")[0].setAttribute("src",data.picture);
})

실행 결과