Skip to content

Commit 6468ac2

Browse files
committed
fix: change SSE connection setting and resolve SSE CORS errors #121
1 parent b9296a0 commit 6468ac2

File tree

3 files changed

+43
-11
lines changed

3 files changed

+43
-11
lines changed

backend/src/main/java/com/example/backend/config/SecurityConfig.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.springframework.web.cors.CorsConfigurationSource;
1313
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
1414

15-
import java.util.Arrays;
1615
import java.util.List;
1716

1817
@Configuration
@@ -36,9 +35,10 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti
3635
public CorsConfigurationSource corsConfigurationSource() {
3736
CorsConfiguration configuration = new CorsConfiguration();
3837
configuration.setAllowedOrigins(List.of(allowedOrigins)); // 프론트엔드 도메인
39-
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")); // 허용할 HTTP 메서드
40-
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Last-Event-ID")); // 허용할 헤더
41-
configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Type", "Cache-Control", "Last-Event-ID")); // 응답에서 노출할 헤더
38+
39+
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"));
40+
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "Last-Event-ID"));
41+
configuration.setExposedHeaders(List.of("Authorization", "Content-Type", "Cache-Control", "Last-Event-ID"));
4242
configuration.setAllowCredentials(true); // 자격 증명 포함 요청 허용
4343

4444
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

backend/src/main/java/com/example/backend/dashboard/controller/CaseDetectController.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
import com.example.backend.dashboard.dto.CaseDetectResponse;
55
import com.example.backend.dashboard.service.CaseDetectService;
66
import com.example.backend.dashboard.service.SseEmitterService;
7+
import jakarta.servlet.http.HttpServletResponse;
78
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.MediaType;
810
import org.springframework.http.ResponseEntity;
911
import org.springframework.web.bind.annotation.*;
1012
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
1113

1214
@RestController
1315
@RequestMapping("/api/v1/case")
1416
@RequiredArgsConstructor
15-
@CrossOrigin("${cors.allowed-origins}")
1617
public class CaseDetectController {
1718

1819
private final CaseDetectService caseDetectService;
@@ -25,8 +26,13 @@ public ResponseEntity<?> detectAlarm(@RequestBody CaseDetectRequest request) {
2526
return ResponseEntity.ok(response);
2627
}
2728

28-
@GetMapping("/subscribe")
29-
public SseEmitter subscribe() {
29+
@GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
30+
public SseEmitter subscribe(HttpServletResponse response) {
31+
32+
response.setHeader("Cache-Control", "no-cache");
33+
response.setHeader("X-Accel-Buffering", "no"); // Nginx 버퍼링 해제
34+
response.setHeader("Connection", "keep-alive"); // 명시적 Keep-Alive
35+
3036
return sseEmitterService.createEmitter();
3137
}
3238

backend/src/main/java/com/example/backend/dashboard/service/SseEmitterService.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package com.example.backend.dashboard.service;
2+
23
import com.example.backend.dashboard.dto.CaseDetectResponse;
34
import org.springframework.http.MediaType;
45
import org.springframework.stereotype.Service;
@@ -7,7 +8,7 @@
78
import java.io.IOException;
89
import java.util.ArrayList;
910
import java.util.List;
10-
import java.util.concurrent.CopyOnWriteArrayList;
11+
import java.util.concurrent.*;
1112

1213
@Service
1314
public class SseEmitterService {
@@ -18,20 +19,45 @@ public SseEmitter createEmitter() {
1819
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
1920
emitters.add(emitter);
2021

21-
emitter.onCompletion(() -> emitters.remove(emitter));
22-
emitter.onTimeout(() -> emitters.remove(emitter));
22+
emitter.onCompletion(() -> {
23+
emitters.remove(emitter);
24+
System.out.println("[SseEmitterService] Emitter completed. Current count = " + emitters.size());
25+
});
26+
27+
emitter.onTimeout(() -> {
28+
emitters.remove(emitter);
29+
System.out.println("[SseEmitterService] Emitter timed out. Current count = " + emitters.size());
30+
});
31+
32+
emitter.onError((e) -> {
33+
emitters.remove(emitter);
34+
System.out.println("[SseEmitterService] Emitter error: " + e.getMessage());
35+
});
36+
37+
try {
38+
emitter.send(SseEmitter.event().name("connect").data("SSE connection established!"));
39+
System.out.println("[SseEmitterService] Sent initial 'connect' event.");
40+
} catch (IOException e) {
41+
emitters.remove(emitter);
42+
System.out.println("[SseEmitterService] Failed to send initial event: " + e.getMessage());
43+
}
44+
2345
return emitter;
2446
}
2547

2648
public void broadcast(CaseDetectResponse response) {
2749
List<SseEmitter> deadEmitters = new ArrayList<>();
50+
2851
for (SseEmitter emitter : emitters) {
2952
try {
3053
emitter.send(SseEmitter.event()
3154
.name("alarm-detected")
32-
.data(response, MediaType.APPLICATION_JSON));
55+
.data(response, MediaType.APPLICATION_JSON)
56+
);
57+
System.out.println(" -> [alarm-detected] sent to " + emitter);
3358
} catch (IOException e) {
3459
deadEmitters.add(emitter);
60+
System.out.println(" -> Failed to send to Emitter: " + emitter);
3561
}
3662
}
3763
emitters.removeAll(deadEmitters);

0 commit comments

Comments
 (0)