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

org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection Maven / Gradle / Ivy

The newest version!
//
//  ========================================================================
//  Copyright (c) 1995-2012 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.io;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.net.websocket.SendResult;

import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.api.CloseException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketConnection;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState;
import org.eclipse.jetty.websocket.common.Generator;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketSession;

/**
 * Provides the implementation of {@link WebSocketConnection} within the framework of the new {@link Connection} framework of jetty-io
 */
public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection
{
    private static final Logger LOG = Log.getLogger(AbstractWebSocketConnection.class);
    private static final Logger LOG_FRAMES = Log.getLogger("org.eclipse.jetty.websocket.io.Frames");

    private final ByteBufferPool bufferPool;
    private final Scheduler scheduler;
    private final Generator generator;
    private final Parser parser;
    private final WebSocketPolicy policy;
    private final FrameQueue queue;
    private final AtomicBoolean suspendToken;
    private WebSocketSession session;
    private List extensions;
    private boolean flushing;
    private boolean isFilling;
    private ConnectionState connectionState;
    private final AtomicBoolean inputClosed;
    private final AtomicBoolean outputClosed;

    public AbstractWebSocketConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy policy, ByteBufferPool bufferPool)
    {
        super(endp,executor);
        this.policy = policy;
        this.bufferPool = bufferPool;
        this.generator = new Generator(policy,bufferPool);
        this.parser = new Parser(policy);
        this.scheduler = scheduler;
        this.extensions = new ArrayList<>();
        this.queue = new FrameQueue();
        this.suspendToken = new AtomicBoolean(false);
        this.connectionState = ConnectionState.CONNECTING;

        this.inputClosed = new AtomicBoolean(false);
        this.outputClosed = new AtomicBoolean(false);
    }

    @Override
    public void assertInputOpen() throws IOException
    {
        if (isInputClosed())
        {
            throw new IOException("Connection input is closed");
        }
    }

    @Override
    public void assertOutputOpen() throws IOException
    {
        if (isOutputClosed())
        {
            throw new IOException("Connection output is closed");
        }
    }

    @Override
    public void close()
    {
        close(StatusCode.NORMAL,null);
    }

    @Override
    public void close(int statusCode, String reason)
    {
        enqueClose(statusCode,reason);
    }

    public  void complete(FrameBytes frameBytes)
    {
        synchronized (queue)
        {
            if (LOG.isDebugEnabled())
            {
                LOG.debug("Completed Write of {} ({} frame(s) in queue)",frameBytes,queue.size());
            }
            flushing = false;
        }
    }

    @Override
    public void disconnect()
    {
        disconnect(false);
    }

    public void disconnect(boolean onlyOutput)
    {
        connectionState = ConnectionState.CLOSED;
        EndPoint endPoint = getEndPoint();
        // We need to gently close first, to allow
        // SSL close alerts to be sent by Jetty
        LOG.debug("Shutting down output {}",endPoint);
        endPoint.shutdownOutput();
        if (!onlyOutput)
        {
            LOG.debug("Closing {}",endPoint);
            endPoint.close();
        }
    }

    /**
     * Enqueue a close frame.
     * 
     * @param statusCode
     *            the WebSocket status code.
     * @param reason
     *            the (optional) reason string. (null is allowed)
     * @see StatusCode
     */
    private void enqueClose(int statusCode, String reason)
    {
        CloseInfo close = new CloseInfo(statusCode,reason);
        try
        {
            outgoingFrame(close.asFrame());
        }
        catch (IOException e)
        {
            LOG.info("Unable to enque close frame",e);
            // TODO: now what?
            disconnect();
        }
    }

    public void flush()
    {
        FrameBytes frameBytes = null;
        ByteBuffer buffer = null;
        synchronized (queue)
        {

            LOG.debug(".flush() - flushing={} - queue.size = {}",flushing,queue.size());
            if (flushing || queue.isEmpty())
            {
                return;
            }

            frameBytes = queue.pop();

            if (!isOpen())
            {
                // No longer have an open connection, drop the frame.
                queue.clear();
                return;
            }

            LOG.debug("Next FrameBytes: {}",frameBytes);

            buffer = frameBytes.getByteBuffer();

            if (buffer == null)
            {
                return;
            }

            flushing = true;

            if (LOG.isDebugEnabled())
            {
                LOG.debug("Flushing {}, {} frame(s) in queue",frameBytes,queue.size());
            }

            if (connectionState != ConnectionState.CLOSED)
            {
                write(buffer,frameBytes);
            }
        }
    }

    public ByteBufferPool getBufferPool()
    {
        return bufferPool;
    }

    /**
     * Get the list of extensions in use.
     * 

* This list is negotiated during the WebSocket Upgrade Request/Response handshake. * * @return the list of negotiated extensions in use. */ public List getExtensions() { return extensions; } public Generator getGenerator() { return generator; } public Parser getParser() { return parser; } @Override public WebSocketPolicy getPolicy() { return this.policy; } public FrameQueue getQueue() { return queue; } @Override public InetSocketAddress getRemoteAddress() { return getEndPoint().getRemoteAddress(); } public Scheduler getScheduler() { return scheduler; } @Override public WebSocketSession getSession() { return session; } @Override public ConnectionState getState() { return connectionState; } @Override public boolean isInputClosed() { return inputClosed.get(); } @Override public boolean isOpen() { return (getState() != ConnectionState.CLOSED) && getEndPoint().isOpen(); } @Override public boolean isOutputClosed() { return outputClosed.get(); } @Override public boolean isReading() { return isFilling; } @Override public void onClose() { super.onClose(); this.connectionState = ConnectionState.CLOSED; } @Override public void onCloseHandshake(boolean incoming, CloseInfo close) { boolean in = inputClosed.get(); boolean out = outputClosed.get(); if (incoming) { in = true; this.inputClosed.set(true); } else { out = true; this.outputClosed.set(true); } LOG.debug("onCloseHandshake({},{}), input={}, output={}",incoming,close,in,out); if (in && out) { LOG.debug("Close Handshake satisfied, disconnecting"); this.disconnect(false); } if (close.isHarsh()) { LOG.debug("Close status code was harsh, disconnecting"); this.disconnect(false); } } @Override public void onFillable() { LOG.debug("{} onFillable()",policy.getBehavior()); ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),false); BufferUtil.clear(buffer); boolean readMore = false; try { isFilling = true; readMore = (read(buffer) != -1); } finally { bufferPool.release(buffer); } if (readMore && (suspendToken.get() == false)) { fillInterested(); } else { isFilling = false; } } @Override public void onOpen() { super.onOpen(); this.connectionState = ConnectionState.OPEN; LOG.debug("fillInterested"); fillInterested(); } @Override protected boolean onReadTimeout() { LOG.debug("Read Timeout. disconnecting connection"); // TODO: notify end user websocket of read timeout? return true; } @Override public Future outgoingFrame(Frame frame) throws IOException { if (LOG.isDebugEnabled()) { LOG.debug("outgoingFrame({})",frame); } Future future = null; synchronized (queue) { FrameBytes bytes = null; if (frame.getType().isControl()) { bytes = new ControlFrameBytes(this,frame); } else { bytes = new DataFrameBytes(this,frame); } future = new JavaxWebsocketFuture(bytes); scheduleTimeout(bytes); if (isOpen()) { if (frame.getType().getOpCode() == OpCode.PING) { queue.prepend(bytes); } else { queue.append(bytes); } } } flush(); return future; } private int read(ByteBuffer buffer) { EndPoint endPoint = getEndPoint(); try { while (true) { int filled = endPoint.fill(buffer); if (filled == 0) { return 0; } else if (filled < 0) { LOG.debug("read - EOF Reached"); return -1; } else { if (LOG.isDebugEnabled()) { LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer)); } parser.parse(buffer); } } } catch (IOException e) { LOG.warn(e); enqueClose(StatusCode.PROTOCOL,e.getMessage()); return -1; } catch (CloseException e) { LOG.warn(e); enqueClose(e.getStatusCode(),e.getMessage()); return -1; } } @Override public void resume() { if (suspendToken.getAndSet(false)) { fillInterested(); } } private void scheduleTimeout(FrameBytes bytes) { if (policy.getIdleTimeout() > 0) { bytes.task = scheduler.schedule(bytes,policy.getIdleTimeout(),TimeUnit.MILLISECONDS); } } /** * Get the list of extensions in use. *

* This list is negotiated during the WebSocket Upgrade Request/Response handshake. * * @param extensions * the list of negotiated extensions in use. */ public void setExtensions(List extensions) { this.extensions = extensions; } @Override public void setSession(WebSocketSession session) { this.session = session; } @Override public SuspendToken suspend() { suspendToken.set(true); return this; } @Override public String toString() { return String.format("%s{g=%s,p=%s}",super.toString(),generator,parser); } private void write(ByteBuffer buffer, FrameBytes frameBytes) { EndPoint endpoint = getEndPoint(); if (LOG_FRAMES.isDebugEnabled()) { LOG_FRAMES.debug("{} Writing {} frame bytes of {}",policy.getBehavior(),buffer.remaining(),frameBytes); } if (connectionState == ConnectionState.CLOSED) { // connection is closed, STOP WRITING, geez. return; } try { endpoint.write(null,frameBytes,buffer); } catch (Throwable t) { frameBytes.failed(null,t); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy