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

com.alipay.api.java_websocket.WebSocketImpl Maven / Gradle / Ivy

Go to download

Alipay openapi SDK for Java Copyright © 2018 杭州蚂蚁金服 All rights reserved. 版权所有 (C)杭州蚂蚁金服 http://open.alipay.com

There is a newer version: 4.40.21.ALL
Show newest version
/*
 * Copyright (c) 2010-2018 Nathan Rajlich
 *
 *  Permission is hereby granted, free of charge, to any person
 *  obtaining a copy of this software and associated documentation
 *  files (the "Software"), to deal in the Software without
 *  restriction, including without limitation the rights to use,
 *  copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following
 *  conditions:
 *
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *  OTHER DEALINGS IN THE SOFTWARE.
 */

package com.alipay.api.java_websocket;

import com.alipay.api.java_websocket.drafts.Draft;
import com.alipay.api.java_websocket.drafts.Draft_6455;
import com.alipay.api.java_websocket.enums.*;
import com.alipay.api.java_websocket.exceptions.IncompleteHandshakeException;
import com.alipay.api.java_websocket.exceptions.InvalidDataException;
import com.alipay.api.java_websocket.exceptions.InvalidHandshakeException;
import com.alipay.api.java_websocket.exceptions.WebsocketNotConnectedException;
import com.alipay.api.java_websocket.framing.CloseFrame;
import com.alipay.api.java_websocket.framing.Framedata;
import com.alipay.api.java_websocket.framing.PingFrame;
import com.alipay.api.java_websocket.handshake.*;
import com.alipay.api.java_websocket.server.WebSocketServer.WebSocketWorker;
import com.alipay.api.java_websocket.util.Charsetfunctions;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Represents one end (client or server) of a single WebSocketImpl connection. Takes care of the "handshake" phase, then allows for easy
 * sending of text frames, and receiving frames through an event-based model.
 */
public class WebSocketImpl implements WebSocket {

    /**
     * Initial buffer size
     */
    public static final int RCVBUF = 16384;

    /**
     * Queue of buffers that need to be sent to the client.
     */
    public final     BlockingQueue outQueue;
    /**
     * Queue of buffers that need to be processed
     */
    public final     BlockingQueue inQueue;
    /**
     * The listener to notify of WebSocket events.
     */
    private final    WebSocketListener         wsl;
    public           SelectionKey              key;
    /**
     * the possibly wrapped channel object whose selection is controlled by {@link #key}
     */
    public           ByteChannel               channel;
    /**
     * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method.
     **/
    public volatile  WebSocketWorker           workerThread; // TODO reset worker?
    /**
     * When true no further frames may be submitted to be sent
     */
    private volatile boolean                   flushandclosestate = false;

    /**
     * The current state of the connection
     */
    private ReadyState readyState = ReadyState.NOT_YET_CONNECTED;

    /**
     * A list of drafts available for this websocket
     */
    private List knownDrafts;

    /**
     * The draft which is used by this websocket
     */
    private Draft draft = null;

    /**
     * The role which this websocket takes in the connection
     */
    private Role role;

    /**
     * the bytes of an incomplete received handshake
     */
    private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate(0);

    /**
     * stores the handshake sent by this websocket ( Role.CLIENT only )
     */
    private ClientHandshake handshakerequest = null;

    private String  closemessage   = null;
    private Integer closecode      = null;
    private Boolean closedremotely = null;

    private String resourceDescriptor = null;

    /**
     * Attribute, when the last pong was recieved
     */
    private long lastPong = System.currentTimeMillis();

    /**
     * Attribut to synchronize the write
     */
    private final Object synchronizeWriteObject = new Object();

    /**
     * Attribute to cache a ping frame
     */
    private PingFrame pingFrame;

    /**
     * Attribute to store connection attachment
     *
     * @since 1.3.7
     */
    private Object attachment;

    /**
     * Creates a websocket with server role
     *
     * @param listener The listener for this instance
     * @param drafts   The drafts which should be used
     */
    public WebSocketImpl(WebSocketListener listener, List drafts) {
        this(listener, (Draft) null);
        this.role = Role.SERVER;
        // draft.copyInstance will be called when the draft is first needed
        if (drafts == null || drafts.isEmpty()) {
            knownDrafts = new ArrayList();
            knownDrafts.add(new Draft_6455());
        } else {
            knownDrafts = drafts;
        }
    }

    /**
     * creates a websocket with client role
     *
     * @param listener The listener for this instance
     * @param draft    The draft which should be used
     */
    public WebSocketImpl(WebSocketListener listener, Draft draft) {
        if (listener == null || (draft == null && role
                == Role.SERVER))// socket can be null because we want do be able to create the object without already having a bound channel
        { throw new IllegalArgumentException("parameters must not be null"); }
        this.outQueue = new LinkedBlockingQueue();
        inQueue = new LinkedBlockingQueue();
        this.wsl = listener;
        this.role = Role.CLIENT;
        if (draft != null) { this.draft = draft.copyInstance(); }
    }

    /**
     * Method to decode the provided ByteBuffer
     *
     * @param socketBuffer the ByteBuffer to decode
     */
    public void decode(ByteBuffer socketBuffer) {
        assert (socketBuffer.hasRemaining());

        if (getReadyState() != ReadyState.NOT_YET_CONNECTED) {
            if (getReadyState() == ReadyState.OPEN) {
                decodeFrames(socketBuffer);
            }
        } else {
            if (decodeHandshake(socketBuffer) && (!isClosing() && !isClosed())) {
                assert (tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer
                        .hasRemaining()); // the buffers will never have remaining bytes at the same time
                if (socketBuffer.hasRemaining()) {
                    decodeFrames(socketBuffer);
                } else if (tmpHandshakeBytes.hasRemaining()) {
                    decodeFrames(tmpHandshakeBytes);
                }
            }
        }
        assert (isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining());
    }

    /**
     * Returns whether the handshake phase has is completed. In case of a broken handshake this will be never the case.
     **/
    private boolean decodeHandshake(ByteBuffer socketBufferNew) {
        ByteBuffer socketBuffer;
        if (tmpHandshakeBytes.capacity() == 0) {
            socketBuffer = socketBufferNew;
        } else {
            if (tmpHandshakeBytes.remaining() < socketBufferNew.remaining()) {
                ByteBuffer buf = ByteBuffer.allocate(tmpHandshakeBytes.capacity() + socketBufferNew.remaining());
                tmpHandshakeBytes.flip();
                buf.put(tmpHandshakeBytes);
                tmpHandshakeBytes = buf;
            }

            tmpHandshakeBytes.put(socketBufferNew);
            tmpHandshakeBytes.flip();
            socketBuffer = tmpHandshakeBytes;
        }
        socketBuffer.mark();
        try {
            HandshakeState handshakestate;
            try {
                if (role == Role.SERVER) {
                    if (draft == null) {
                        for (Draft d : knownDrafts) {
                            d = d.copyInstance();
                            try {
                                d.setParseMode(role);
                                socketBuffer.reset();
                                Handshakedata tmphandshake = d.translateHandshake(socketBuffer);
                                if (!(tmphandshake instanceof ClientHandshake)) {
                                    closeConnectionDueToWrongHandshake(
                                            new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "wrong http function"));
                                    return false;
                                }
                                ClientHandshake handshake = (ClientHandshake) tmphandshake;
                                handshakestate = d.acceptHandshakeAsServer(handshake);
                                if (handshakestate == HandshakeState.MATCHED) {
                                    resourceDescriptor = handshake.getResourceDescriptor();
                                    ServerHandshakeBuilder response;
                                    try {
                                        response = wsl.onWebsocketHandshakeReceivedAsServer(this, d, handshake);
                                    } catch (InvalidDataException e) {
                                        closeConnectionDueToWrongHandshake(e);
                                        return false;
                                    } catch (RuntimeException e) {
                                        wsl.onWebsocketError(this, e);
                                        closeConnectionDueToInternalServerError(e);
                                        return false;
                                    }
                                    write(d.createHandshake(d.postProcessHandshakeResponseAsServer(handshake, response), role));
                                    draft = d;
                                    open(handshake);
                                    return true;
                                }
                            } catch (InvalidHandshakeException e) {
                                // go on with an other draft
                            }
                        }
                        if (draft == null) {
                            closeConnectionDueToWrongHandshake(new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "no draft matches"));
                        }
                        return false;
                    } else {
                        // special case for multiple step handshakes
                        Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
                        if (!(tmphandshake instanceof ClientHandshake)) {
                            flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
                            return false;
                        }
                        ClientHandshake handshake = (ClientHandshake) tmphandshake;
                        handshakestate = draft.acceptHandshakeAsServer(handshake);

                        if (handshakestate == HandshakeState.MATCHED) {
                            open(handshake);
                            return true;
                        } else {
                            close(CloseFrame.PROTOCOL_ERROR, "the handshake did finally not match");
                        }
                        return false;
                    }
                } else if (role == Role.CLIENT) {
                    draft.setParseMode(role);
                    Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
                    if (!(tmphandshake instanceof ServerHandshake)) {
                        flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
                        return false;
                    }
                    ServerHandshake handshake = (ServerHandshake) tmphandshake;
                    handshakestate = draft.acceptHandshakeAsClient(handshakerequest, handshake);
                    if (handshakestate == HandshakeState.MATCHED) {
                        try {
                            wsl.onWebsocketHandshakeReceivedAsClient(this, handshakerequest, handshake);
                        } catch (InvalidDataException e) {
                            flushAndClose(e.getCloseCode(), e.getMessage(), false);
                            return false;
                        } catch (RuntimeException e) {
                            wsl.onWebsocketError(this, e);
                            flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false);
                            return false;
                        }
                        open(handshake);
                        return true;
                    } else {
                        close(CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake");
                    }
                }
            } catch (InvalidHandshakeException e) {
                close(e);
            }
        } catch (IncompleteHandshakeException e) {
            if (tmpHandshakeBytes.capacity() == 0) {
                socketBuffer.reset();
                int newsize = e.getPreferedSize();
                if (newsize == 0) {
                    newsize = socketBuffer.capacity() + 16;
                } else {
                    assert (e.getPreferedSize() >= socketBuffer.remaining());
                }
                tmpHandshakeBytes = ByteBuffer.allocate(newsize);

                tmpHandshakeBytes.put(socketBufferNew);
                // tmpHandshakeBytes.flip();
            } else {
                tmpHandshakeBytes.position(tmpHandshakeBytes.limit());
                tmpHandshakeBytes.limit(tmpHandshakeBytes.capacity());
            }
        }
        return false;
    }

    private void decodeFrames(ByteBuffer socketBuffer) {
        List frames;
        try {
            frames = draft.translateFrame(socketBuffer);
            for (Framedata f : frames) {
                draft.processFrame(this, f);
            }
        } catch (InvalidDataException e) {
            wsl.onWebsocketError(this, e);
            close(e);
        }
    }

    /**
     * Close the connection if the received handshake was not correct
     *
     * @param exception the InvalidDataException causing this problem
     */
    private void closeConnectionDueToWrongHandshake(InvalidDataException exception) {
        write(generateHttpResponseDueToError(404));
        flushAndClose(exception.getCloseCode(), exception.getMessage(), false);
    }

    /**
     * Close the connection if there was a server error by a RuntimeException
     *
     * @param exception the RuntimeException causing this problem
     */
    private void closeConnectionDueToInternalServerError(RuntimeException exception) {
        write(generateHttpResponseDueToError(500));
        flushAndClose(CloseFrame.NEVER_CONNECTED, exception.getMessage(), false);
    }

    /**
     * Generate a simple response for the corresponding endpoint to indicate some error
     *
     * @param errorCode the http error code
     * @return the complete response as ByteBuffer
     */
    private ByteBuffer generateHttpResponseDueToError(int errorCode) {
        String errorCodeDescription;
        switch (errorCode) {
            case 404:
                errorCodeDescription = "404 WebSocket Upgrade Failure";
                break;
            case 500:
            default:
                errorCodeDescription = "500 Internal Server Error";
        }
        return ByteBuffer.wrap(Charsetfunctions.asciiBytes(
                "HTTP/1.1 " + errorCodeDescription + "\r\nContent-Type: text/html\nServer: TooTallNate Java-WebSocket\r\nContent-Length: "
                        + (48 + errorCodeDescription.length()) + "\r\n\r\n

" + errorCodeDescription + "

")); } public synchronized void close(int code, String message, boolean remote) { if (getReadyState() != ReadyState.CLOSING && readyState != ReadyState.CLOSED) { if (getReadyState() == ReadyState.OPEN) { if (code == CloseFrame.ABNORMAL_CLOSE) { assert (!remote); setReadyState(ReadyState.CLOSING); flushAndClose(code, message, false); return; } if (draft.getCloseHandshakeType() != CloseHandshakeType.NONE) { try { if (!remote) { try { wsl.onWebsocketCloseInitiated(this, code, message); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); } } if (isOpen()) { CloseFrame closeFrame = new CloseFrame(); closeFrame.setReason(message); closeFrame.setCode(code); closeFrame.isValid(); sendFrame(closeFrame); } } catch (InvalidDataException e) { wsl.onWebsocketError(this, e); flushAndClose(CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false); } } flushAndClose(code, message, remote); } else if (code == CloseFrame.FLASHPOLICY) { assert (remote); flushAndClose(CloseFrame.FLASHPOLICY, message, true); } else if (code == CloseFrame.PROTOCOL_ERROR) { // this endpoint found a PROTOCOL_ERROR flushAndClose(code, message, remote); } else { flushAndClose(CloseFrame.NEVER_CONNECTED, message, false); } setReadyState(ReadyState.CLOSING); tmpHandshakeBytes = null; return; } } public void close(int code, String message) { close(code, message, false); } /** * This will close the connection immediately without a proper close handshake. The code and the message therefore won't be transfered * over the wire also they will be forwarded to onClose/onWebsocketClose. * * @param code the closing code * @param message the closing message * @param remote Indicates who "generated" code.
* true means that this endpoint received the code from the other endpoint.
* false means this endpoint decided to send the given code,
* remote may also be true if this endpoint started the closing handshake since the other endpoint may not * simply echo the code but close the connection the same time this endpoint does do but with an other * code.
**/ public synchronized void closeConnection(int code, String message, boolean remote) { if (getReadyState() == ReadyState.CLOSED) { return; } //Methods like eot() call this method without calling onClose(). Due to that reason we have to adjust the ReadyState manually if (getReadyState() == ReadyState.OPEN) { if (code == CloseFrame.ABNORMAL_CLOSE) { setReadyState(ReadyState.CLOSING); } } if (key != null) { // key.attach( null ); //see issue #114 key.cancel(); } if (channel != null) { try { channel.close(); } catch (IOException e) { if (e.getMessage().equals("Broken pipe")) { } else { wsl.onWebsocketError(this, e); } } } try { this.wsl.onWebsocketClose(this, code, message, remote); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); } if (draft != null) { draft.reset(); } handshakerequest = null; setReadyState(ReadyState.CLOSED); } protected void closeConnection(int code, boolean remote) { closeConnection(code, "", remote); } public void closeConnection() { if (closedremotely == null) { throw new IllegalStateException("this method must be used in conjunction with flushAndClose"); } closeConnection(closecode, closemessage, closedremotely); } public void closeConnection(int code, String message) { closeConnection(code, message, false); } public synchronized void flushAndClose(int code, String message, boolean remote) { if (flushandclosestate) { return; } closecode = code; closemessage = message; closedremotely = remote; flushandclosestate = true; wsl.onWriteDemand(this); // ensures that all outgoing frames are flushed before closing the connection try { wsl.onWebsocketClosing(this, code, message, remote); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); } if (draft != null) { draft.reset(); } handshakerequest = null; } public void eot() { if (getReadyState() == ReadyState.NOT_YET_CONNECTED) { closeConnection(CloseFrame.NEVER_CONNECTED, true); } else if (flushandclosestate) { closeConnection(closecode, closemessage, closedremotely); } else if (draft.getCloseHandshakeType() == CloseHandshakeType.NONE) { closeConnection(CloseFrame.NORMAL, true); } else if (draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY) { if (role == Role.SERVER) { closeConnection(CloseFrame.ABNORMAL_CLOSE, true); } else { closeConnection(CloseFrame.NORMAL, true); } } else { closeConnection(CloseFrame.ABNORMAL_CLOSE, true); } } public void close(int code) { close(code, "", false); } public void close(InvalidDataException e) { close(e.getCloseCode(), e.getMessage(), false); } /** * Send Text data to the other end. * * @throws NotYetConnectedException websocket is not yet connected */ public void send(String text) throws WebsocketNotConnectedException { if (text == null) { throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl."); } send(draft.createFrames(text, role == Role.CLIENT)); } /** * Send Binary data (plain bytes) to the other end. * * @throws IllegalArgumentException the data is null * @throws NotYetConnectedException websocket is not yet connected */ public void send(ByteBuffer bytes) throws IllegalArgumentException, WebsocketNotConnectedException { if (bytes == null) { throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl."); } send(draft.createFrames(bytes, role == Role.CLIENT)); } public void send(byte[] bytes) throws IllegalArgumentException, WebsocketNotConnectedException { send(ByteBuffer.wrap(bytes)); } private void send(Collection frames) { if (!isOpen()) { throw new WebsocketNotConnectedException(); } if (frames == null) { throw new IllegalArgumentException(); } ArrayList outgoingFrames = new ArrayList(); for (Framedata f : frames) { outgoingFrames.add(draft.createBinaryFrame(f)); } write(outgoingFrames); } public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) { send(draft.continuousFrame(op, buffer, fin)); } public void sendFrame(Collection frames) { send(frames); } public void sendFrame(Framedata framedata) { send(Collections.singletonList(framedata)); } public void sendPing() throws NotYetConnectedException { if (pingFrame == null) { pingFrame = new PingFrame(); } sendFrame(pingFrame); } public boolean hasBufferedData() { return !this.outQueue.isEmpty(); } public void startHandshake(ClientHandshakeBuilder handshakedata) throws InvalidHandshakeException { // Store the Handshake Request we are about to send this.handshakerequest = draft.postProcessHandshakeRequestAsClient(handshakedata); resourceDescriptor = handshakedata.getResourceDescriptor(); assert (resourceDescriptor != null); // Notify Listener try { wsl.onWebsocketHandshakeSentAsClient(this, this.handshakerequest); } catch (InvalidDataException e) { // Stop if the client code throws an exception throw new InvalidHandshakeException("Handshake data rejected by client."); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); throw new InvalidHandshakeException("rejected because of " + e); } // Send write(draft.createHandshake(this.handshakerequest, role)); } private void write(ByteBuffer buf) { outQueue.add(buf); wsl.onWriteDemand(this); } /** * Write a list of bytebuffer (frames in binary form) into the outgoing queue * * @param bufs the list of bytebuffer */ private void write(List bufs) { synchronized (synchronizeWriteObject) { for (ByteBuffer b : bufs) { write(b); } } } private void open(Handshakedata d) { setReadyState(ReadyState.OPEN); try { wsl.onWebsocketOpen(this, d); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); } } public boolean isOpen() { return getReadyState() == ReadyState.OPEN; } public boolean isClosing() { return getReadyState() == ReadyState.CLOSING; } public boolean isFlushAndClose() { return flushandclosestate; } public boolean isClosed() { return getReadyState() == ReadyState.CLOSED; } public ReadyState getReadyState() { return readyState; } private void setReadyState(ReadyState ReadyState) { this.readyState = ReadyState; } public String toString() { return super.toString(); // its nice to be able to set breakpoints here } public InetSocketAddress getRemoteSocketAddress() { return wsl.getRemoteSocketAddress(this); } public InetSocketAddress getLocalSocketAddress() { return wsl.getLocalSocketAddress(this); } public Draft getDraft() { return draft; } public void close() { close(CloseFrame.NORMAL); } public String getResourceDescriptor() { return resourceDescriptor; } /** * Getter for the last pong recieved * * @return the timestamp for the last recieved pong */ long getLastPong() { return lastPong; } /** * Update the timestamp when the last pong was received */ public void updateLastPong() { this.lastPong = System.currentTimeMillis(); } /** * Getter for the websocket listener * * @return the websocket listener associated with this instance */ public WebSocketListener getWebSocketListener() { return wsl; } @SuppressWarnings("unchecked") public T getAttachment() { return (T) attachment; } public void setAttachment(T attachment) { this.attachment = attachment; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy