All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.fhframework.WebSocketRFC6455HeartbeatExecutor Maven / Gradle / Ivy

package pl.fhframework;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.PingMessage;
import org.springframework.web.socket.WebSocketSession;
import pl.fhframework.core.util.EventEmitter;

import javax.annotation.PreDestroy;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Consumer;

/**
 * Created by krzysztof.kobylarek on 2017-03-15.
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class WebSocketRFC6455HeartbeatExecutor implements EventEmitter {

    public static final String key = WebSocketRFC6455HeartbeatExecutor.class.getName();

    private static long DEFAULT_PING_INTERVAL = 30; // seconds

    @Autowired @Qualifier("webSocketTaskScheduler")
    private ThreadPoolTaskScheduler taskScheduler;

    @Value("${websocket.ping_interval}") // seconds
    private Long pingInterval;

    private ScheduledFuture pingFuture;

    private WebSocketSession webSocketSession;

    private PingTask pingTask = null;

    private boolean cancelScheduling = false;

    private Object pingTaskGuard = new Object();

    private Random random = new Random();

    @Getter
    private List> listeners = new ArrayList<>();

    @PreDestroy
    private void onDestroy(){
        if (pingTask!=null) {
            pingTask.cancel();
        }
        if (pingFuture!=null){
            pingFuture.cancel(true);
        }
    }

    public void setWebSocketSession(WebSocketSession webSocketSession) {
        this.webSocketSession = webSocketSession;
    }

    public synchronized void schedulePing(){
        if (canSchedule()) {
            if (pingTask == null)
                pingTask = new PingTask();

            if (!pingTask.isScheduled()){
                synchronized (pingTaskGuard) {
                    if (!pingTask.isScheduled()) {
                        pingTask.reset();
                        Date timeToPing = new Date(System.currentTimeMillis()
                                + coalesce(pingInterval, DEFAULT_PING_INTERVAL) * 1000) ;

                        pingFuture = taskScheduler.schedule(pingTask, timeToPing);
                        pingTask.setScheduled(true);
                    }
                }
            }
        }
    }

    public synchronized void cancelPing() {
        if (pingFuture!=null && !pingFuture.isCancelled()){
            pingFuture.cancel(false); // try to complete task
            pingFuture = null;
        }

        if (pingTask!=null){
            pingTask.cancel();
            pingTask = null;
        }
        cancelScheduling = true;
    }

    public void sendPing() {
        try {
            long pingTimestamp = System.currentTimeMillis();
            // ByteBuffer.putLong() -> WebSocket -> ByteBuffer.getLong() gives totally other long value (other bytes, not only order)
            ByteBuffer timestampBuffer = ByteBuffer.wrap(String.valueOf(pingTimestamp).getBytes());
            fireEvent(pingTimestamp);
            webSocketSession.sendMessage(new PingMessage(timestampBuffer));
        } catch (IOException ex){
            cancelPing();
        }
    }

    boolean canSchedule(){
        return canSend() && !cancelScheduling;
    }

    boolean canSend(){
        return webSocketSession.isOpen();
    }

    private class PingTask implements Runnable {

        private boolean sendFlag;
        private boolean scheduled;

        void reset(){
            sendFlag=true; scheduled=false;
        }

        @Override
        public void run() {

            try {
                if (sendFlag && canSend()) {
                    sendPing();
                }
            }  finally {
                sendFlag=false;
                scheduled=false;
                schedulePing();
            }
        }

        void cancel(){
            sendFlag=false;
        }

        void setScheduled(boolean flag){
            scheduled=flag;
        }

        public boolean isScheduled() {
            return scheduled;
        }
    }

    static private  T coalesce(T... ts){
        for (T t : ts){
            if (t!=null)
                return t;
        }
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy