외부 API로부터 실시간 가격데이터를 가져오고, 이를 통해 알림 보내기 1 - WebSocket연결하기

HiroPark·2022년 10월 7일
0

Spring

목록 보기
9/11

구현하고자 하는 기능은 다음과 같습니다

  • Coincap의 api를 통하여 코인 가격을 실시간으로 가져옵니다
    https://docs.coincap.io/#9d1f0874-aa46-420c-9091-8bf9859c0743

  • 유저는 자신이 원하는 자산을 선택할 수 있고(비트코인, 이더리움...), 그것의 현재가격을 확인 한 뒤 원하는 상/하한 가격 혹은 상/하향 퍼센티지를 등록합니다.

  • 유저가 설정한 가격 또는 (현재가격 기준)퍼센티지를 넘어서 가격이 변동하면 이에 대한 웹 푸시 알림 주기.

일단은 비트코인 단일 자산의 가격을 웹 소켓으로 가져온 뒤, 이에 대한 하한 가격 알림을 만드는 것을 목표로 잡고, 여기에 퍼센티지, 상한 알림등을 추가하려 합니다. 웹소켓도 처음이고, 푸시 알림도 처음이다 보니 이들을 먼저 성공적으로 구현한 뒤에 살을 붙이면 될 것 같습니다.

CoinCap api에 대한 웹소켓 클라이언트를 우선 만들어보겠습니다.

https://stackoverflow.com/questions/26452903/javax-websocket-client-simple-example

  • 해당 링크의 첫번째 답변을 토대로(복사 붙여넣기) 하여 기능을 구현했습니다

WebsocketClientEndpoint.java

@ClientEndpoint
public class WebsocketClientEndpoint {
    Session userSession = null;
    private MessageHandler messageHandler;

    public WebsocketClientEndpoint() {
    }

    public Session connect(URI endpointURI) {
        try {
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            userSession = container.connectToServer(this, endpointURI);

            return userSession;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Callback hook for Connection open events.
     *
     * @param userSession the userSession which is opened.
     */
    @OnOpen
    public void onOpen(Session userSession) {
        System.out.println("opening websocket");
        this.userSession = userSession;
    }

    /**
     * Callback hook for Connection close events.
     *
     * @param userSession the userSession which is getting closed.
     * @param reason the reason for connection close
     */
    @OnClose
    public void onClose(Session userSession, CloseReason reason) {
        System.out.println("closing websocket");
        this.userSession = null;
    }

    /**
     * Callback hook for Message Events. This method will be invoked when a client send a message.
     *
     * @param message The text message
     */
    @OnMessage
    public void onMessage(String message) throws ParseException, IOException {
        if (this.messageHandler != null) {
            this.messageHandler.handleMessage(message);
        }
    }

    @OnMessage
    public void onMessage(ByteBuffer bytes) {
        System.out.println("Handle byte buffer");
    }

    /**
     * register message handler
     *
     * @param msgHandler
     */
    public void addMessageHandler(MessageHandler msgHandler) {
        this.messageHandler = msgHandler;
    }

    /**
     * Send a message.
     *
     * @param message
     */
    public void sendMessage(String message) {
        this.userSession.getAsyncRemote().sendText(message);
    }

    /**
     * Message handler.
     *
     * @author Jiji_Sasidharan
     */
    public static interface MessageHandler {
        public void handleMessage(String message) throws ParseException, IOException;
    }
}
  • @ClientEndpoint : 해당 자바 객체가(POJO) 웹소켓 클라이언트라는 것을 나타냅니다. 이 애노테이션이 달린 객체들은, 메소드 레벨의 annotation을 이용하여, 웹소켓 라이프사이클에 해당하는 메소드들을 사용할 수 있습니다

  • Session, MessageHandler : 두 웹소켓 엔드포인트간의 대화를 나타냅니다. 엔드포인트에서 세션에 MessageHandler를 등록함으로써 들어오는 메시지들을 관리할 수 있습니다.

  • 세션을 반환하는 connect 메소드를 통해 웹소켓 서버와의 연결 기능을 만듭니다.
    - 외부로의 웹소켓 연결을 위한 새로운 WebSocketContainer 를 만든 뒤, 이를 통해 서버와 연결해줍니다. 이때 세션을 반환하여 후에 세션을 닫을때 이를 사용해줄것입니다.

  • @OnOpen과 @OnClose를 통해 웹소켓이 열리고 닫힐때 세션을 할당하고/ 없애줍니다

  • MessageHandler를 통해 들어오는 메시지를 관리해줄 수 있습니다. MessageHandler 인터페이스를 생성하고, handleMessage() 메소드를 추가합니다. 이를 구현 객체는 이를 구현하여 원하는 방식으로 메세지를 관리할것입니다.
    이 MessageHandler를 addMessageHandler 메소드를 통해 클래스에 등록해줍니다.

이제 웹소켓 클라이언트 객체를 구현했으니, 이를 통해서 실제로 소켓을 열고 가격 정보를 받아올 시간입니다.

AlertService - AlertUser()

public void AlertUser() {
         Alert alert = alertRepository.findAll().get(0);
         double SetPrice = alert.getPrice();
        JSONParser jsonParser = new JSONParser();

        final NotificationRequest build = NotificationRequest.builder()
                .title("bitcoin alert")
                .message(SetPrice + "broke down")
                .token(notificationService.getToken(userDetailService.returnUser().getEmail()))
                .build();

        try {
            final WebsocketClientEndpoint clientEndPoint = new WebsocketClientEndpoint();

            Session session = clientEndPoint.connect(new URI("wss://ws.coincap.io/prices?assets=bitcoin"));

            WebsocketClientEndpoint.MessageHandler handler = new WebsocketClientEndpoint.MessageHandler() {
                public void handleMessage(String message) throws ParseException, IOException {
                    Object obj = jsonParser.parse(message);

                    JSONObject jsonObject = (JSONObject) obj;

                    double price = Double.parseDouble(jsonObject.get("bitcoin").toString());
                    System.out.println(price);

                    if (price < SetPrice) {
                        System.out.println("끝");
                        notificationService.sendNotification(build);
                        session.close();
                    }

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        System.err.println("InterruptedException exception: " + ex.getMessage());
                    }
                }
            };

            clientEndPoint.addMessageHandler(handler);

        } catch (URISyntaxException ex) {
            System.err.println("URISyntaxException exception: " + ex.getMessage());
        }
    }
  • clientEndPoint 를 생성하고, 이를 coincap api 서버에 연결해줍니다.
  • 이제 handleMesssage()메소드를 구현하여 들어오는 가격 데이터를 원하는대로 조작해줄 수 있으면 됩니다.
  • 들어오는 String형태의 가격을 json형식으로 변환해줍니다

    {"bitcoin":"19970.29"}

과 같은 형식으로 값이 변환됩니다.

  • parseDouble을 통하여 이 값을 double로 변환한 뒤, 이 가격이 SetPrice(유저가 설정한 가격의 하한선) 밑으로 내려가면, 알림을 보내주고(sendNotification , FCM을 활용한 푸시알림) 세션을 닫습니다

  • 이때, Thread.sleep(1000)을 사용하여, 1초마다 쓰레드를 일시정지시켜 값을 일정한 속도로 가져오게 합니다.

  • 이렇게 구현한 MessageHandler를 clientEndPoint 에 등록해주면, 원하는 대로 SetPrice 밑으로 가격이 내려가면 웹소켓이 닫히고 알림이 옵니다.

profile
https://de-vlog.tistory.com/ 이사중입니다

0개의 댓글