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

org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint Maven / Gradle / Ivy

There is a newer version: 11.0.0.beta1
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.websocket.common;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
import org.eclipse.jetty.websocket.common.frames.ContinuationFrame;
import org.eclipse.jetty.websocket.common.frames.DataFrame;
import org.eclipse.jetty.websocket.common.frames.PingFrame;
import org.eclipse.jetty.websocket.common.frames.PongFrame;
import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;

/**
 * Endpoint for Writing messages to the Remote websocket.
 */
public class WebSocketRemoteEndpoint implements RemoteEndpoint
{
    /** Message Type*/
    private enum MsgType 
    {
        BLOCKING,
        ASYNC,
        STREAMING,
        PARTIAL_TEXT,
        PARTIAL_BINARY
    };
    
    private static final WriteCallback NOOP_CALLBACK = new WriteCallback()
    {
        @Override
        public void writeSuccess()
        {
        }
        
        @Override
        public void writeFailed(Throwable x)
        {
        }
    };

    private static final Logger LOG = Log.getLogger(WebSocketRemoteEndpoint.class);
    public final LogicalConnection connection;
    public final OutgoingFrames outgoing;
    /** JSR-356 blocking send behaviour message and Type sanity to support partial send properly */

    private final static int ASYNC_MASK =       0x0000FFFF;
    private final static int BLOCK_MASK =       0x00010000;
    private final static int STREAM_MASK =      0x00020000;
    private final static int PARTIAL_TEXT_MASK= 0x00040000;
    private final static int PARTIAL_BINARY_MASK= 0x00080000;
    
    private final AtomicInteger msgState = new AtomicInteger();
    
    private final BlockingWriteCallback blocker = new BlockingWriteCallback();

    public WebSocketRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoing)
    {
        if (connection == null)
        {
            throw new IllegalArgumentException("LogicalConnection cannot be null");
        }
        this.connection = connection;
        this.outgoing = outgoing;
    }

    private void blockingWrite(WebSocketFrame frame) throws IOException
    {
        uncheckedSendFrame(frame,blocker);
        blocker.block();
    }

    private boolean lockMsg(MsgType type)
    {
        // Blocking -> BLOCKING  ; Async -> ASYNC     ; Partial -> PARTIAL_XXXX ; Stream -> STREAMING
        // Blocking -> Pending!! ; Async -> BLOCKING  ; Partial -> Pending!!    ; Stream -> STREAMING 
        // Blocking -> BLOCKING  ; Async -> ASYNC     ; Partial -> Pending!!    ; Stream -> STREAMING
        // Blocking -> Pending!! ; Async -> STREAMING ; Partial -> Pending!!    ; Stream -> STREAMING
        // Blocking -> Pending!! ; Async -> Pending!! ; Partial -> PARTIAL_TEXT ; Stream -> Pending!!
        // Blocking -> Pending!! ; Async -> Pending!! ; Partial -> PARTIAL_BIN  ; Stream -> Pending!!
        
        while(true)
        {
            int state = msgState.get();
            
            switch (type)
            {
                case BLOCKING:
                    if ((state&(PARTIAL_BINARY_MASK+PARTIAL_TEXT_MASK))!=0)
                        throw new IllegalStateException(String.format("Partial message pending %x for %s",state,type));
                    if ((state&BLOCK_MASK)!=0)
                        throw new IllegalStateException(String.format("Blocking message pending %x for %s",state,type));
                    if (msgState.compareAndSet(state,state|BLOCK_MASK))
                        return state==0;
                    break;
                    
                case ASYNC:
                    if ((state&(PARTIAL_BINARY_MASK+PARTIAL_TEXT_MASK))!=0)
                        throw new IllegalStateException(String.format("Partial message pending %x for %s",state,type));
                    if ((state&ASYNC_MASK)==ASYNC_MASK)
                        throw new IllegalStateException(String.format("Too many async sends: %x",state));
                    if (msgState.compareAndSet(state,state+1))
                        return state==0;
                    break;
                    
                case STREAMING:
                    if ((state&(PARTIAL_BINARY_MASK+PARTIAL_TEXT_MASK))!=0)
                        throw new IllegalStateException(String.format("Partial message pending %x for %s",state,type));
                    if ((state&STREAM_MASK)!=0)
                        throw new IllegalStateException(String.format("Already streaming %x for %s",state,type));
                    if (msgState.compareAndSet(state,state|STREAM_MASK))
                        return state==0;
                    break;
                
                case PARTIAL_BINARY:
                    if (state==PARTIAL_BINARY_MASK)
                        return false;
                    if (state==0)
                    {
                        if (msgState.compareAndSet(0,state|PARTIAL_BINARY_MASK))
                            return true;
                    }
                    throw new IllegalStateException(String.format("Cannot send %s in state %x",type,state));
                    
                case PARTIAL_TEXT:
                    if (state==PARTIAL_TEXT_MASK)
                        return false;
                    if (state==0)
                    {
                        if (msgState.compareAndSet(0,state|PARTIAL_TEXT_MASK))
                            return true;
                    }
                    throw new IllegalStateException(String.format("Cannot send %s in state %x",type,state));
            }
        }
    }

    private void unlockMsg(MsgType type)
    {
        while(true)
        {
            int state = msgState.get();
            
            switch (type)
            {
                case BLOCKING:
                    if ((state&BLOCK_MASK)==0)
                        throw new IllegalStateException(String.format("Not Blocking in state %x",state));
                    if (msgState.compareAndSet(state,state&~BLOCK_MASK))
                        return;
                    break;
                    
                case ASYNC:
                    if ((state&ASYNC_MASK)==0)
                        throw new IllegalStateException(String.format("Not Async in %x",state));
                    if (msgState.compareAndSet(state,state-1))
                        return;
                    break;
                    
                case STREAMING:
                    if ((state&STREAM_MASK)==0)
                        throw new IllegalStateException(String.format("Not Streaming in state %x",state));
                    if (msgState.compareAndSet(state,state&~STREAM_MASK))
                        return;
                    break;
                
                case PARTIAL_BINARY:
                    if (msgState.compareAndSet(PARTIAL_BINARY_MASK,0))
                        return;
                    throw new IllegalStateException(String.format("Not Partial Binary in state %x",state));
                    
                case PARTIAL_TEXT:
                    if (msgState.compareAndSet(PARTIAL_TEXT_MASK,0))
                        return;
                    throw new IllegalStateException(String.format("Not Partial Text in state %x",state));
                    
            }
        }
    }
    
    
    public InetSocketAddress getInetSocketAddress()
    {
        return connection.getRemoteAddress();
    }

    /**
     * Internal
     * 
     * @param frame
     *            the frame to write
     * @return the future for the network write of the frame
     */
    private Future sendAsyncFrame(WebSocketFrame frame)
    {
        FutureWriteCallback future = new FutureWriteCallback();
        uncheckedSendFrame(frame,future);
        return future;
    }

    /**
     * Blocking write of bytes.
     */
    @Override
    public void sendBytes(ByteBuffer data) throws IOException
    {
        lockMsg(MsgType.BLOCKING);
        try
        {
            connection.getIOState().assertOutputOpen();
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendBytes with {}",BufferUtil.toDetailString(data));
            }
            blockingWrite(new BinaryFrame().setPayload(data));
        }
        finally
        {
            unlockMsg(MsgType.BLOCKING);
        }
    }

    @Override
    public Future sendBytesByFuture(ByteBuffer data)
    {
        lockMsg(MsgType.ASYNC);
        try
        {
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendBytesByFuture with {}",BufferUtil.toDetailString(data));
            }
            return sendAsyncFrame(new BinaryFrame().setPayload(data));
        }
        finally
        {
            unlockMsg(MsgType.ASYNC);
        }
    }

    @Override
    public void sendBytes(ByteBuffer data, WriteCallback callback)
    {
        lockMsg(MsgType.ASYNC);
        try
        {
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendBytes({}, {})",BufferUtil.toDetailString(data),callback);
            }
            uncheckedSendFrame(new BinaryFrame().setPayload(data),callback==null?NOOP_CALLBACK:callback);
        }
        finally
        {
            unlockMsg(MsgType.ASYNC);
        }
    }

    /* ------------------------------------------------------------ */
    /** unchecked send
     * @param frame
     * @param callback
     */
    public void uncheckedSendFrame(WebSocketFrame frame, WriteCallback callback)
    {
        try
        {
            connection.getIOState().assertOutputOpen();
            outgoing.outgoingFrame(frame,callback);
        }
        catch (IOException e)
        {
            callback.writeFailed(e);
        }
    }

    @Override
    public void sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException
    {
        boolean first=lockMsg(MsgType.PARTIAL_BINARY);
        try
        {
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendPartialBytes({}, {})",BufferUtil.toDetailString(fragment),isLast);
            }
            DataFrame frame = first?new BinaryFrame():new ContinuationFrame();
            frame.setPayload(fragment);
            frame.setFin(isLast);
            blockingWrite(frame);
        }
        finally
        {
            if (isLast)
                unlockMsg(MsgType.PARTIAL_BINARY);
        }
    }

    @Override
    public void sendPartialString(String fragment, boolean isLast) throws IOException
    {
        boolean first=lockMsg(MsgType.PARTIAL_TEXT);
        try
        {
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendPartialString({}, {})",fragment,isLast);
            }
            DataFrame frame = first?new TextFrame():new ContinuationFrame();
            frame.setPayload(BufferUtil.toBuffer(fragment,StandardCharsets.UTF_8));
            frame.setFin(isLast);
            blockingWrite(frame);
        }
        finally
        {
            if (isLast)
                unlockMsg(MsgType.PARTIAL_TEXT);
        }
    }

    @Override
    public void sendPing(ByteBuffer applicationData) throws IOException
    {
        if (LOG.isDebugEnabled())
        {
            LOG.debug("sendPing with {}",BufferUtil.toDetailString(applicationData));
        }
        sendAsyncFrame(new PingFrame().setPayload(applicationData));
    }

    @Override
    public void sendPong(ByteBuffer applicationData) throws IOException
    {
        if (LOG.isDebugEnabled())
        {
            LOG.debug("sendPong with {}",BufferUtil.toDetailString(applicationData));
        }
        sendAsyncFrame(new PongFrame().setPayload(applicationData));
    }

    @Override
    public void sendString(String text) throws IOException
    {
        lockMsg(MsgType.BLOCKING);
        try
        {
            WebSocketFrame frame = new TextFrame().setPayload(text);
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendString with {}",BufferUtil.toDetailString(frame.getPayload()));
            }
            blockingWrite(frame);
        }
        finally
        {
            unlockMsg(MsgType.BLOCKING);
        }
    }

    @Override
    public Future sendStringByFuture(String text)
    {
        lockMsg(MsgType.ASYNC);
        try
        {
            TextFrame frame = new TextFrame().setPayload(text);
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendStringByFuture with {}",BufferUtil.toDetailString(frame.getPayload()));
            }
            return sendAsyncFrame(frame);  
        }
        finally
        {
            unlockMsg(MsgType.ASYNC);
        }
    }

    @Override
    public void sendString(String text, WriteCallback callback)
    {
        lockMsg(MsgType.ASYNC);
        try
        {
            TextFrame frame = new TextFrame().setPayload(text);
            if (LOG.isDebugEnabled())
            {
                LOG.debug("sendString({},{})",BufferUtil.toDetailString(frame.getPayload()),callback);
            }
            uncheckedSendFrame(frame,callback==null?NOOP_CALLBACK:callback);
        }
        finally
        {
            unlockMsg(MsgType.ASYNC);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy