이전 포스팅에서는 자바에서 Python 파일을 직접 실행하여 한 줄 씩 결과 값을 Log로 출력하는 것을 해보았다. 이번에는 서버에서 출력하는 값을 실시간으로 클라이언트로 가져와 뷰에서 값이 출력하는 것을 해볼 것이다.
Websocket 통신으로 SockJs와 Stomp를 사용하였다.
실시간 양방향 통신을 위한 프로토콜. 서버와 클라이언트 간 웹 기반의 애플리케이션에서 데이터를 전송하기 위해 사용된다.
WebSocket의 대안으로 사용되는 JavaScript 라이브러리. 브라우저 웹 서버 사이 짧은 지연시간과 크로스 브라우징을 지원하며 Websocket이 지원안되는 최신 브라우저에서도 잘 작동되도록 해준다.(Fallback)
간단한 텍스트 기반 메시징 프로토콜. 중개자 역할을 하는 서버와 클라이언트 간의 표준 메시징 프로토콜이다.
나는 좀더 빠르고 가벼운 SockJs를 Stomp 를 함께 사용하기로 하였다.
HTML 파일에 SockJS 클라이언트 라이브러리와 stomp 라이브러리를 추가한다. CDN이나 npm 등 원하는 방식대로 설치 후 추가. (편의를 위해 jQuery도 같이 추가하였다.)
<!-- jquery-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<!--sockJs-->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<!--stomp-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.maven.artifact.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.maven.artifact.version}</version>
</dependency>
WebSocketConfig.java라는 새로운 클래스를 생성하여 WebSocket 구성을 수행한다.
@Configuration
@EnableWebSocketMessageBroker
@Slf4j
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
/topic
은 클라이언트가 구독할 경로로 사용된다./app/hello
와 같은 경로로 메시지를 전송하면 서버에서는 /hello
경로로 해당 메시지를 처리한다.@MessageMapping("/start")
public void start(String filePath) throws Exception {
new ProcessExecutor().command("python", filePath)
.redirectOutput(new LogOutputStream() {
@Override
protected void processLine(String line) {
messagingTemplate.convertAndSend("/topic/progress", line);
}
}).execute();
}
/app/start
) 해당 메서드가 실행된다. /topic/process
)에 추가하고 있다.<button type="button" id="start">파일 실행하기</button>
<!--라이브러리 추가 코드 생략-->
<script>
let socket = null;
let stomp = null;
$(document).ready(()=>{
connectStomp();
})
$("#start").on("click", ()=>{
// 파이썬 파일 실행
stomp.send("/app/start", {}, "FILE_PATH")
})
function connectStomp() {
socket = new SockJS("/stomp"); // endpoint
stomp = Stomp.over(socket);
stomp.connect({}, function () {
console.log("Connected stomp!");
// 파일 정보 및 진행률이 전달되는 토픽 구독
stomp.subscribe('/topic/progress', function (event) {
console.log(event.body)
});
});
}
</script>
(gif는 event 자체를 출력하고 있지만 event.body를 출력해야하는 것이 맞다..)
디버그 메시지를 출력되지 않도록 하고 싶다면 해당 옵션을 추가하면 된다.
stompClient.debug = null
현재 문자열로 출력되고 있는 tqdm을 오브젝트로 만들어서 progress bar로 뷰에 좀 더 예쁘게 출력될 수 있도록 해보고 싶었다.
70%|███████ | 7/10 [00:07<00:03, 1.01s/it]
위의 문자열을 정규식을 활용하여 [진행도(%)]와 [현재 작업], [전체 작업수], [소요 시간], [남은 시간], [작업 당 평균 처리 시간]으로 분리한다.
진행도를 표현할 수 있는 progress bar는 progressbar
라이브러리를 사용하였다.
회사에서는 현재 전자정부프레임워크를 기반한 spring mvc를 사용하고 있다. 그런데 xml 또는 Java Config에 잘못된 부분이 있는지, 계속 연결하는 부분에서 404 에러가 발생하였다. 확인해보니 web.xml 설정 부분에서
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>.do</url-pattern>
<url-pattern>/</url-pattern>
</servlet-mapping>
url-pattern 을 .do
로 설정해 발생한 에러였다. Websocket 엔드포인트(/stomp/*
)로 추가 설정하더라도 변함없이 에러가 발생하여 일단은 default 경로(/
)로 설정하여 정상 연결되도록 하고 있다. 그러나 가끔씩 서버 및 배포를 재시작하면 리소스를 불러오는데 에러가 발생하여 추후 수정이 필요하다.
다음 포스팅에서는 클라이언트 측에서 전달해야하는 인자값이 없는 경우 양방향 통신을 할 필요는 없으므로 단방향 통신 중 하나인 SSE(Server-Sent-Event) 를 했던 과정을 작성해 보겠다!
url-pattern이 저도 *.do 로 되어있는데 방법이 없는건가요?