codememo

Spring WebSocket @SendToSession: 특정 세션으로 메시지 보내기

tipmemo 2023. 8. 26. 11:56
반응형

Spring WebSocket @SendToSession: 특정 세션으로 메시지 보내기

특정 세션에 메시지를 보낼 수 있습니까?

클라이언트와 Spring 서블릿 간에 인증되지 않은 웹 소켓이 있습니다.비동기 작업이 종료되면 특정 연결로 요청되지 않은 메시지를 보내야 합니다.

@Controller
public class WebsocketTest {


     @Autowired
    public SimpMessageSendingOperations messagingTemplate;

    ExecutorService executor = Executors.newSingleThreadExecutor();

    @MessageMapping("/start")
    public void start(SimpMessageHeaderAccessor accessor) throws Exception {
        String applicantId=accessor.getSessionId();        
        executor.submit(() -> {
            //... slow job
            jobEnd(applicantId);
        });
    }

    public void jobEnd(String sessionId){
        messagingTemplate.convertAndSend("/queue/jobend"); //how to send only to that session?
    }
}

이 코드에서 볼 수 있듯이 클라이언트는 비동기 작업을 시작할 수 있으며 작업이 완료되면 종료 메시지가 필요합니다.분명히, 저는 지원자에게만 메시지를 보내야 하고 모든 사람에게 방송을 하면 안 됩니다.당신과 함께라면 정말 좋을 것입니다.@SendToSession주석 또는messagingTemplate.convertAndSendToSession방법.

갱신하다

시도해 봤습니다.

messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));

그러나 지정된 세션뿐만 아니라 모든 세션으로 브로드캐스트됩니다.

업데이트 2

convertAndSendToUser() 메서드를 사용하여 테스트합니다.이 테스트는 공식적인 봄 튜토리얼의 해킹입니다. https://spring.io/guides/gs/messaging-stomp-websocket/

다음은 서버 코드입니다.

@Controller
public class WebsocketTest {

    @PostConstruct
    public void init(){
        ScheduledExecutorService statusTimerExecutor=Executors.newSingleThreadScheduledExecutor();
        statusTimerExecutor.scheduleAtFixedRate(new Runnable() {                
            @Override
            public void run() {
                messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"));
            }
        }, 5000,5000, TimeUnit.MILLISECONDS);
    } 

     @Autowired
        public SimpMessageSendingOperations messagingTemplate;
}

다음은 클라이언트 코드입니다.

function connect() {
            var socket = new WebSocket('ws://localhost:8080/hello');
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function(frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                stompClient.subscribe('/user/queue/test', function(greeting){
                    console.log(JSON.parse(greeting.body));
                });
            });
        }

유감스럽게도 클라이언트는 예상대로 5000ms마다 세션별 회신을 받지 못합니다."1"은 연결된 두 번째 클라이언트에 유효한 sessionId입니다. 디버그 모드에서 볼 수 있기 때문입니다.SimpMessageHeaderAccessor.getSessionId()

배경 시나리오

원격 작업에 대한 진행률 표시줄을 만들고 싶습니다. 클라이언트가 서버에 비동기 작업을 요청하고 서버에서 보낸 웹 소켓 메시지로 진행률을 확인합니다.이것은 파일 업로드가 아니라 원격 계산이므로 서버만 각 작업의 진행 상황을 알 수 있습니다.각 작업이 세션별로 시작되기 때문에 특정 세션에 메시지를 보내야 합니다.클라이언트에서 원격 계산 서버가 이 작업을 시작하도록 요청하고 모든 작업 단계에 대해 작업 진행 상태가 표시된 지원자 클라이언트에 회신합니다.클라이언트는 작업에 대한 메시지를 받고 진행률/상태 표시줄을 작성합니다.이것이 제가 세션별 메시지가 필요한 이유입니다.사용자별 메시지를 사용할 수도 있지만 Spring은 사용자별로 요청되지 않은 메시지를 제공하지 않습니다. (Spring Websocket으로 사용자 메시지를 보낼없음)

작동 솔루션

 __      __ ___   ___  _  __ ___  _  _   ___      ___   ___   _    _   _  _____  ___  ___   _  _ 
 \ \    / // _ \ | _ \| |/ /|_ _|| \| | / __|    / __| / _ \ | |  | | | ||_   _||_ _|/ _ \ | \| |
  \ \/\/ /| (_) ||   /| ' <  | | | .` || (_ |    \__ \| (_) || |__| |_| |  | |   | || (_) || .` |
   \_/\_/  \___/ |_|_\|_|\_\|___||_|\_| \___|    |___/ \___/ |____|\___/   |_|  |___|\___/ |_|\_|

UPDATE2 솔루션부터 마지막 매개 변수(메시지)로 convertAndSendToUser 메서드를 완료해야 했습니다.머리글):

messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));

어디에createHeaders()다음 방법입니다.

private MessageHeaders createHeaders(String sessionId) {
        SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
        headerAccessor.setSessionId(sessionId);
        headerAccessor.setLeaveMutable(true);
        return headerAccessor.getMessageHeaders();
    }

특정 목적지를 생성할 필요가 없으며, 봄 4.1부터 이미 즉시 사용할 수 있습니다(SPR-11309 참조).

는 "지된사는자용정"에합니다./user/queue/something은 다음과 같은 수 .

SimpMessageSendingOperations Javaadoc에 명시된 바와 같이 사용자 이름이 실제로 sessionId이기 때문에 그렇지 않으면 헤더로도 설정해야 합니다.DefaultUserDestinationResolver메시지를 라우트할 수 없으며 메시지를 삭제합니다.

SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
    .create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);

messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload, 
    headerAccessor.getMessageHeaders());

사용자를 인증할 필요가 없습니다.

그것은 매우 복잡하고 제 생각에는 그럴 가치가 없습니다.인증되지 않은 사용자라도 세션 ID를 기준으로 모든 사용자에 대한 헤드라인 등록해야 합니다.

모든 사용자가 자신의 고유 대기열에만 가입한다고 가정해 보겠습니다.

stompClient.subscribe('/session/specific' + uuid, handler);

서버에서 사용자가 등록하기 전에 특정 세션에 대해 통지하고 메시지를 발송한 후 지도에 저장해야 합니다.

    @MessageMapping("/putAnonymousSession/{sessionId}")
    public void start(@DestinationVariable sessionId) throws Exception {
        anonymousUserSession.put(key, sessionId);
    }

그런 다음 사용자에게 메시지를 보내려면 다음 작업을 수행해야 합니다.

messagingTemplate.convertAndSend("/session/specific" + key); 

하지만 저는 당신이 무엇을 하려고 하는지, 그리고 어떻게 특정 세션(익명인)을 찾을지 잘 모르겠습니다.

세션 ID를 다음에 추가하기만 하면 됩니다.

  • 서버측

    convertAndSendToUser(sessionId,apiName,responseObject);

  • 클라이언트 측

    $stopp.subscribe('/user/+sessionId+'/apiName', 처리기);

참조:
추가하는 것을 잊지 마십시오.'/user'서버 측의 엔드포인트에 있습니다.

저는 같은 문제로 어려움을 겪고 있었지만 제시된 해결책이 저에게 효과가 없었기 때문에 다른 접근 방식을 취해야 했습니다.

  1. 사용자가 세션 ID로 식별되도록 웹 소켓 구성을 수정합니다.
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-endpoint")
                .setHandshakeHandler(new DefaultHandshakeHandler() {
                    
                    @Override
                    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
                        if (request instanceof ServletServerHttpRequest) {
                            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                            HttpSession session = servletRequest.getServletRequest().getSession();
                            return new Principal() {
                                @Override
                                public String getName() {
                                    return session.getId();
                                }
                            };
                        } else {
                            return null;
                        }
                    }
                }).withSockJS();
    }
  1. 해당 세션 ID로 메시지 보내기(헤더 없음):
    simpMessagingTemplate.convertAndSendToUser(sessionId, "/queue", payload);

가장 간단한 방법은 @SendToUser의 브로드캐스트 매개 변수를 활용하는 것입니다.문서:

메시지를 사용자와 관련된 모든 세션으로 보낼 것인지, 처리 중인 입력 메시지의 세션으로만 보낼 것인지 여부.기본적으로 이 값은 true로 설정되어 있으며, 이 경우 메시지는 모든 세션에 브로드캐스트됩니다.

당신의 정확한 경우에는 이렇게 보일 것입니다.

    @MessageMapping("/start")
    @SendToUser(value = "/queue/jobend", broadcast = false)
    //...

언급URL : https://stackoverflow.com/questions/34929578/spring-websocket-sendtosession-send-message-to-specific-session

반응형