0+ 스프링/0 + SpringBoot(스프링부트)

Websocket Server headers 추가 방법(javax.websocket, spring boot)

힘들면힘을내는쿼카 2022. 5. 18. 16:25
728x90
반응형

 

 

웹소켓 프로젝트 중

웹소켓 헤더를 수정할 일이 발생했다.

검색 중에 한국어도 정리된 내용이없어서 이에 대한 내용을 정리하려고 한다.

 

먼저 현재 구현된 웹소켓 코드부터 보자..!

 

1. configuration

Gradle에 의존성을 추가한다. (매우매우매우매우 중요!!!!!!!!!!!!!!!!)

// https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api
compileOnly group: 'javax.websocket', name: 'javax.websocket-api'

 

@Configuration
public class ServerConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {

    private static volatile BeanFactory context;

    @Override
    public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
        Object serverConfigurator = context.getBean("serverConfigurator");

        return context.getBean(clazz);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ServerConfigurator.context = applicationContext;
    }
    
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {

        String protocolName = "ocpp1.6";

        List<String> headerValue = new ArrayList<>();
        headerValue.add(protocolName);

        response.getHeaders().put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, headerValue);
    }
}

 

반응형

 

2. endpoint

아래 코드를 보면 

@ServerEndpoint에서 configurator = ServerConfigurator.class로 설정함을 알 수 있다.

@Component
@RequiredArgsConstructor
@ServerEndpoint(value ="/endpoint/{id}", configurator = ServerConfigurator.class)
public class Socket {
   
    @OnOpen
    public void onOpen(Session session, @PathParam("id") String cpid) {
       
    }

    @OnClose
    public void onClose(Session session, @PathParam("id") String cpid) {
        
    }

    @OnError
    public void onError(Session session, Throwable throwable, @PathParam("id") String cpid) {
        
    }

    @OnMessage
    public void onMessage(Session session, String rawMessage, @PathParam("id") String cpid) {

    }
}

 

 

클라이언트에 맞게 응답을 줌을 알 수 있다.

 

Handshake Details
Request URL: http://localhost:8081/CSMS/OCPPJ/0019001Request Method: GETStatus Code: 101
▶Request
HeadersSec-WebSocket-Version: 13
Sec-WebSocket-Key: cfkx7NnsDNdwb0KzQzJb5A==
Connection: UpgradeUpgrade: websocket
Sec-WebSocket-Protocol: ocpp1.6, ocpp1.5
Sec-WebSocket-Extensions: permessage-deflate;
client_max_window_bitsHost: localhost:8081

▶Response
HeadersUpgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: u3wcncSRaesx0QFHF590zeOel8w=
Sec-WebSocket-Extensions: permessage-deflate;
client_max_window_bits=15
Sec-WebSocket-Protocol: ocpp1.6
Date: Wed, 18 May 2022 05:47:32 GMT

 

 

이거 구현하려고 반나절이나 걸렸다.. ㅠ0ㅠ

막힌 부분은 바로 이부분이다...

 @Override
 public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {

        String protocolName = "ocpp1.6";

        List<String> headerValue = new ArrayList<>();
        headerValue.add(protocolName);

        response.getHeaders().put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, headerValue);
    }

 

 

처음에 modifyHandshake가 override가 안되서 많이 삽질했다..

아니 왜 안되는거야!!!!

꾸역꾸역 찾은 결과 gardle에 의존성을 추가하니 해결되었다... 너무 행복했다.. 😀😀

// https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api
compileOnly group: 'javax.websocket', name: 'javax.websocket-api'

 


 

javax.websocket 동작 원리

https://stackoverflow.com/questions/25992111/how-does-serverendpointconfig-configurator-work

 

How does ServerEndpointConfig.Configurator work

It is coming from Ordering of filters, servlets in Jetty-9.2.2 As mentioned "split out the security logic into a standalone class that your Servlet Filters and your custom javax.websocket.server.

stackoverflow.com

 

HTTP 업그레이드(websocket이라고도 함) 요청이 도착하면 어떻게 됩니까?

  1. 컨테이너는 웹 소켓에 대한 업그레이드 요청임을 나타내는 다양한 헤더가 있음을 확인합니다.
    • 요청 방법은 GET무엇입니까?
    • Upgrade헤더 값 입니까 WebSocket?
    • Sec-WebSocket-Key헤더 값이 존재하고 유효합니까 ?
    • Sec-WebSocket-Version헤더 값이 존재하고 유효합니까 ?
  2. 컨테이너는 들어오는 요청에 무언가가 매핑되어 있는지 찾으려고 시도합니다(서블릿 경로 매핑 규칙과 javax.websocket uri 템플릿 경로 매핑 규칙의 조합 사용).
  3. 등록된 끝점이 위의 규칙에서 검색되면 열린 것으로 간주되기 전에 초기화 및 구성해야 합니다.
  4. 해당 ServerEndpointConfig끝점에 대해 사용됩니다(모든 끝점에는 이 클래스가 등록되어 있고, 일부는 기본값이고, 일부는 주석에서 계산되고, 일부는 ServerContainer.addEndpoint(ServerEndpointConfig)메서드를 통해 제공됩니다.
  5. 다음 순서는 웹 소켓 끝점을 초기화하는 데 사용됩니다.
    1. 컨테이너 는 들어오는 원시 업그레이드 요청 HandshakeRequest을 나타내는 개체를 설정합니다.HandshakeResponse
    2. Configurator컨테이너는 다음 에서 얻습니다.ServerEndpointConfig.getConfigurator()
    3. 컨테이너 호출Configurator.modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response)
    4. 컨테이너 호출 Configurator.checkOrigin(String origin)- 이것이 실패하면 즉시 오류 403 Forbidden이 반환됩니다.
    5. 컨테이너 호출 Configurator.getNegotiatedSubprotocol(List<String> supported, List<String> requested)- 하위 프로토콜이 제공되면 WebSocket 업그레이드 응답 헤더에 사용됩니다.Sec-WebSocket-Protocol
    6. 컨테이너 호출 Configurator.getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)- 반환된 값은 WebSocket 업그레이드 응답 헤더에 사용됩니다.Sec-WebSocket-Extensions
    7. Configurator.getEndpointInstance(Class<T> endpointClass)생성 된 엔드포인트 클래스 매개변수를 사용한 컨테이너 호출 ServerEndpointConfig.
    8. 이 시점에서 Endpoint+ Session가 생성되고 websocket이 열립니다.

언제든지 이 요청을 업그레이드하지 않으려면 getEndpointInstance클래스에서 예외를 던지십시오. 추천합니다 java.lang.InstantiationException. 이로 인해 Jetty가 업그레이드를 수행하지 않고 서블릿 처리 체인으로 요청을 보냅니다.

그러나 Configurator에서 http 응답이 어떻게 보이는지에 대한 옵션은 매우 제한적입니다(JSR-356/javax.websocket 사양에 따라 정의되지 않음).

 

 

 

728x90
반응형