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

org.red5.net.websocket.server.WsRemoteEndpointImplServer Maven / Gradle / Ivy

package org.red5.net.websocket.server;

import java.io.EOFException;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;

import javax.websocket.SendHandler;
import javax.websocket.SendResult;

import org.apache.coyote.http11.upgrade.UpgradeInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.websocket.Transformation;
import org.apache.tomcat.websocket.WsRemoteEndpointImplBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is the server side {@link javax.websocket.RemoteEndpoint} implementation - i.e. what the server uses to send data to the client.
 */
public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {

    private static final StringManager sm = StringManager.getManager(WsRemoteEndpointImplServer.class);

    private Logger log = LoggerFactory.getLogger(WsRemoteEndpointImplServer.class); // must not be static

    private final SocketWrapperBase socketWrapper;

    private final UpgradeInfo upgradeInfo;

    private final WsWriteTimeout wsWriteTimeout;

    private volatile SendHandler handler;

    private volatile ByteBuffer[] buffers;

    private volatile long timeoutExpiry = -1;

    private volatile boolean close;

    public WsRemoteEndpointImplServer(SocketWrapperBase socketWrapper, UpgradeInfo upgradeInfo) {
        this.socketWrapper = socketWrapper;
        this.upgradeInfo = upgradeInfo;
        // Paul: our impl doesnt use the WsServerContainer.wsWriteTimeout due to its type and what may be registered, we use our impl classes
        this.wsWriteTimeout = new WsWriteTimeout();
    }

    @Override
    protected final boolean isMasked() {
        return false;
    }

    @Override
    protected void doWrite(SendHandler handler, long blockingWriteTimeoutExpiry, ByteBuffer... buffers) {
        if (blockingWriteTimeoutExpiry == -1) {
            this.handler = handler;
            this.buffers = buffers;
            // This is definitely the same thread that triggered the write so a dispatch will be required.
            onWritePossible(true);
        } else {
            // Blocking
            try {
                for (ByteBuffer buffer : buffers) {
                    long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis();
                    if (timeout <= 0) {
                        SendResult sr = new SendResult(new SocketTimeoutException());
                        handler.onResult(sr);
                        return;
                    }
                    socketWrapper.setWriteTimeout(timeout);
                    socketWrapper.write(true, buffer);
                }
                long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis();
                if (timeout <= 0) {
                    SendResult sr = new SendResult(new SocketTimeoutException());
                    handler.onResult(sr);
                    return;
                }
                socketWrapper.setWriteTimeout(timeout);
                socketWrapper.flush(true);
                handler.onResult(SENDRESULT_OK);
            } catch (IOException e) {
                SendResult sr = new SendResult(e);
                handler.onResult(sr);
            }
        }
    }

    @Override
    protected void updateStats(long payloadLength) {
        upgradeInfo.addMsgsSent(1);
        upgradeInfo.addBytesSent(payloadLength);
    }

    public void onWritePossible(boolean useDispatch) {
        ByteBuffer[] buffers = this.buffers;
        if (buffers == null) {
            // Servlet 3.1 will call the write listener once even if nothing was written
            return;
        }
        boolean complete = false;
        try {
            socketWrapper.flush(false);
            // If this is false there will be a call back when it is true
            while (socketWrapper.isReadyForWrite()) {
                complete = true;
                for (ByteBuffer buffer : buffers) {
                    if (buffer.hasRemaining()) {
                        complete = false;
                        socketWrapper.write(false, buffer);
                        break;
                    }
                }
                if (complete) {
                    socketWrapper.flush(false);
                    complete = socketWrapper.isReadyForWrite();
                    if (complete) {
                        wsWriteTimeout.unregister(this);
                        clearHandler(null, useDispatch);
                        if (close) {
                            close();
                        }
                    }
                    break;
                }
            }
        } catch (IOException | IllegalStateException e) {
            wsWriteTimeout.unregister(this);
            clearHandler(e, useDispatch);
            close();
        }
        if (!complete) {
            // Async write is in progress
            long timeout = getSendTimeout();
            if (timeout > 0) {
                // Register with timeout thread
                timeoutExpiry = timeout + System.currentTimeMillis();
                wsWriteTimeout.register(this);
            }
        }
    }

    @Override
    protected void doClose() {
        if (handler != null) {
            // close() can be triggered by a wide range of scenarios. It is far simpler just to always use a dispatch than it is to try and track
            // whether or not this method was called by the same thread that triggered the write
            clearHandler(new EOFException(), true);
        }
        // no ex thrown here anymore, handled elsewhere in lib
        socketWrapper.close();
        wsWriteTimeout.unregister(this);
    }

    protected long getTimeoutExpiry() {
        return timeoutExpiry;
    }

    /*
     * Currently this is only called from the background thread so we could just call clearHandler() with useDispatch == false but the method parameter was added in case other callers
     * started to use this method to make sure that those callers think through what the correct value of useDispatch is for them.
     */
    protected void onTimeout(boolean useDispatch) {
        if (handler != null) {
            clearHandler(new SocketTimeoutException(), useDispatch);
        }
        close();
    }

    @Override
    protected void setTransformation(Transformation transformation) {
        // Overridden purely so it is visible to other classes in this package
        super.setTransformation(transformation);
    }

    /**
     *
     * @param t
     *            The throwable associated with any error that occurred
     * @param useDispatch
     *            Should {@link SendHandler#onResult(SendResult)} be called from a new thread, keeping in mind the requirements of {@link javax.websocket.RemoteEndpoint.Async}
     */
    private void clearHandler(Throwable t, boolean useDispatch) {
        // Setting the result marks this (partial) message as complete which means the next one may be sent which
        // could update the value of the handler. Therefore, keep a local copy before signalling the end of the (partial)
        // message.
        SendHandler sh = handler;
        handler = null;
        buffers = null;
        if (sh != null) {
            if (useDispatch) {
                OnResultRunnable r = new OnResultRunnable(sh, t);
                AbstractEndpoint endpoint = socketWrapper.getEndpoint();
                Executor containerExecutor = endpoint.getExecutor();
                if (endpoint.isRunning() && containerExecutor != null) {
                    containerExecutor.execute(r);
                } else {
                    // Can't use the executor so call the runnable directly.
                    // This may not be strictly specification compliant in all cases but during shutdown only close messages are going
                    // to be sent so there should not be the issue of nested calls leading to stack overflow as described in bug
                    // 55715. The issues with nested calls was the reason for the separate thread requirement in the specification.
                    r.run();
                }
            } else {
                if (t == null) {
                    sh.onResult(new SendResult());
                } else {
                    sh.onResult(new SendResult(t));
                }
            }
        }
    }

    private static class OnResultRunnable implements Runnable {

        private final SendHandler sh;

        private final Throwable t;

        private OnResultRunnable(SendHandler sh, Throwable t) {
            this.sh = sh;
            this.t = t;
        }

        @Override
        public void run() {
            if (t == null) {
                sh.onResult(new SendResult());
            } else {
                sh.onResult(new SendResult(t));
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy