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

org.glassfish.tyrus.client.TyrusClientEngine Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2014 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.glassfish.tyrus.client;

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.websocket.ClientEndpointConfig;
import javax.websocket.CloseReason;
import javax.websocket.Extension;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.HandshakeRequest;

import org.glassfish.tyrus.core.ExtendedExtension;
import org.glassfish.tyrus.core.Frame;
import org.glassfish.tyrus.core.FramingException;
import org.glassfish.tyrus.core.Handshake;
import org.glassfish.tyrus.core.ProtocolHandler;
import org.glassfish.tyrus.core.RequestContext;
import org.glassfish.tyrus.core.TyrusEndpoint;
import org.glassfish.tyrus.core.TyrusEndpointWrapper;
import org.glassfish.tyrus.core.TyrusExtension;
import org.glassfish.tyrus.core.TyrusRemoteEndpoint;
import org.glassfish.tyrus.core.TyrusWebSocket;
import org.glassfish.tyrus.core.Utils;
import org.glassfish.tyrus.core.Version;
import org.glassfish.tyrus.core.frame.CloseFrame;
import org.glassfish.tyrus.spi.ClientContainer;
import org.glassfish.tyrus.spi.ClientEngine;
import org.glassfish.tyrus.spi.Connection;
import org.glassfish.tyrus.spi.ReadHandler;
import org.glassfish.tyrus.spi.UpgradeRequest;
import org.glassfish.tyrus.spi.UpgradeResponse;
import org.glassfish.tyrus.spi.Writer;

/**
 * @author Pavel Bucek (pavel.bucek at oracle.com)
 */
public class TyrusClientEngine implements ClientEngine {

    private static final Logger LOGGER = Logger.getLogger(TyrusClientEngine.class.getName());
    private static final int DEFAULT_INCOMING_BUFFER_SIZE = 4194315; // 4M (payload) + 11 (frame overhead)

    private static final Version DEFAULT_VERSION = Version.DRAFT17;
    private static final int BUFFER_STEP_SIZE = 256;

    private final ProtocolHandler protocolHandler = DEFAULT_VERSION.createHandler(true);
    private final TyrusEndpointWrapper endpointWrapper;
    private final ClientHandshakeListener listener;
    private final Map properties;

    private Handshake clientHandShake = null;
    private volatile TimeoutHandler timeoutHandler = null;


    /**
     * Create {@link org.glassfish.tyrus.spi.WebSocketEngine} instance based on passed {@link WebSocketContainer} and with configured maximal
     * incoming buffer size.
     *
     * @param endpointWrapper wrapped client endpoint.
     * @param listener        used for reporting back the outcome of handshake. {@link ClientHandshakeListener#onSessionCreated(javax.websocket.Session)}
     *                        is invoked if handshake is completed and provided {@link Session} is open and ready to be
     *                        returned from {@link WebSocketContainer#connectToServer(Class, javax.websocket.ClientEndpointConfig, java.net.URI)}
     *                        (and alternatives) call.
     * @param properties      passed container properties, see {@link org.glassfish.tyrus.client.ClientManager#getProperties()}.
     */
    /* package */ TyrusClientEngine(TyrusEndpointWrapper endpointWrapper, ClientHandshakeListener listener, Map properties) {
        this.endpointWrapper = endpointWrapper;
        this.listener = listener;
        this.properties = properties;
    }

    @Override
    public UpgradeRequest createUpgradeRequest(URI uri, TimeoutHandler timeoutHandler) {
        this.timeoutHandler = timeoutHandler;
        clientHandShake = protocolHandler.createClientHandShake(RequestContext.Builder.create().requestURI(uri).build());

        ClientEndpointConfig config = (ClientEndpointConfig) endpointWrapper.getEndpointConfig();

        clientHandShake.setExtensions(config.getExtensions());
        clientHandShake.setSubProtocols(config.getPreferredSubprotocols());

        clientHandShake.prepareRequest();
        config.getConfigurator().beforeRequest(clientHandShake.getRequest().getHeaders());

        return clientHandShake.getRequest();
    }

    @Override
    public Connection processResponse(UpgradeResponse upgradeResponse, final Writer writer, final Connection.CloseListener closeListener) {

        try {
            clientHandShake.validateServerResponse(upgradeResponse);

            final TyrusWebSocket tyrusWebSocket = new TyrusWebSocket(protocolHandler, new TyrusEndpoint(endpointWrapper));
            final List handshakeResponseExtensions = TyrusExtension.fromHeaders(upgradeResponse.getHeaders().get(HandshakeRequest.SEC_WEBSOCKET_EXTENSIONS));
            final List extensions = new ArrayList();

            final ExtendedExtension.ExtensionContext extensionContext = new ExtendedExtension.ExtensionContext() {

                private final Map properties = new HashMap();

                @Override
                public Map getProperties() {
                    return properties;
                }
            };

            for (Extension responseExtension : handshakeResponseExtensions) {
                for (Extension installedExtension : ((ClientEndpointConfig) endpointWrapper.getEndpointConfig()).getExtensions()) {
                    if (responseExtension.getName() != null && responseExtension.getName().equals(installedExtension.getName())) {

                        if (installedExtension instanceof ExtendedExtension) {
                            ((ExtendedExtension) installedExtension).onHandshakeResponse(extensionContext, responseExtension.getParameters());
                        }

                        extensions.add(installedExtension);
                    }
                }
            }

            final Session sessionForRemoteEndpoint = endpointWrapper.createSessionForRemoteEndpoint(
                    new TyrusRemoteEndpoint(tyrusWebSocket),
                    upgradeResponse.getFirstHeaderValue(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL),
                    extensions);

            ((ClientEndpointConfig) endpointWrapper.getEndpointConfig()).getConfigurator().afterResponse(upgradeResponse);

            protocolHandler.setWriter(writer);
            protocolHandler.setWebSocket(tyrusWebSocket);
            protocolHandler.setExtensions(extensions);
            protocolHandler.setExtensionContext(extensionContext);

            tyrusWebSocket.onConnect(this.clientHandShake.getRequest());

            listener.onSessionCreated(sessionForRemoteEndpoint);

            final Object o = properties.get(ClientContainer.INCOMING_BUFFER_SIZE);
            final int incomingBufferSize;
            if (o != null && o instanceof Integer) {
                incomingBufferSize = (Integer) o;
            } else {
                incomingBufferSize = DEFAULT_INCOMING_BUFFER_SIZE;
            }

            return new Connection() {

                private final ReadHandler readHandler = new TyrusReadHandler(protocolHandler, tyrusWebSocket, incomingBufferSize, sessionForRemoteEndpoint.getNegotiatedExtensions(), extensionContext);

                @Override
                public ReadHandler getReadHandler() {
                    return readHandler;
                }

                @Override
                public Writer getWriter() {
                    return writer;
                }

                @Override
                public CloseListener getCloseListener() {
                    return closeListener;
                }

                @Override
                public void close(CloseReason reason) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        Logger.getLogger(this.getClass().getName()).log(Level.WARNING, e.getMessage(), e);
                    }

                    tyrusWebSocket.close(reason.getCloseCode().getCode(), reason.getReasonPhrase());

                    for (Extension extension : sessionForRemoteEndpoint.getNegotiatedExtensions()) {
                        if (extension instanceof ExtendedExtension) {
                            ((ExtendedExtension) extension).destroy(extensionContext);
                        }
                    }

                }
            };
        } catch (Throwable e) {
            listener.onError(e);
            return null;
        }
    }

    /**
     * Get {@link TimeoutHandler} associated with current {@link ClientEngine} instance.
     *
     * @return timeout handler instance or {@code null} when not present.
     */
    public TimeoutHandler getTimeoutHandler() {
        return timeoutHandler;
    }

    /**
     * Called when response is received from the server.
     */
    public static interface ClientHandshakeListener {

        /**
         * Invoked when handshake is completed and provided {@link Session} is open and ready to be
         * returned from {@link WebSocketContainer#connectToServer(Class, javax.websocket.ClientEndpointConfig, java.net.URI)}
         * (and alternatives) call.
         *
         * @param session opened client session.
         */
        public void onSessionCreated(Session session);


        /**
         * Called when an error is found in handshake response.
         *
         * @param exception error found during handshake response check.
         */
        public void onError(Throwable exception);
    }

    private static class TyrusReadHandler implements ReadHandler {

        private final int incomingBufferSize;
        private final ProtocolHandler handler;
        private final TyrusWebSocket webSocket;
        private final List negotiatedExtensions;
        private final ExtendedExtension.ExtensionContext extensionContext;

        private ByteBuffer buffer = null;

        TyrusReadHandler(final ProtocolHandler protocolHandler, final TyrusWebSocket webSocket, int incomingBufferSize, List negotiatedExtensions, ExtendedExtension.ExtensionContext extensionContext) {
            this.handler = protocolHandler;
            this.webSocket = webSocket;
            this.incomingBufferSize = incomingBufferSize;
            this.negotiatedExtensions = negotiatedExtensions;
            this.extensionContext = extensionContext;

            protocolHandler.setExtensionContext(extensionContext);
        }

        @Override
        public void handle(ByteBuffer data) {
            try {
                if (data != null && data.hasRemaining()) {

                    if (buffer != null) {
                        data = Utils.appendBuffers(buffer, data, incomingBufferSize, BUFFER_STEP_SIZE);
                    } else {
                        int newSize = data.remaining();
                        if (newSize > incomingBufferSize) {
                            throw new IllegalArgumentException("Buffer overflow.");
                        } else {
                            final int roundedSize = (newSize % BUFFER_STEP_SIZE) > 0 ? ((newSize / BUFFER_STEP_SIZE) + 1) * BUFFER_STEP_SIZE : newSize;
                            final ByteBuffer result = ByteBuffer.allocate(roundedSize > incomingBufferSize ? newSize : roundedSize);
                            result.flip();
                            data = Utils.appendBuffers(result, data, incomingBufferSize, BUFFER_STEP_SIZE);
                        }
                    }

                    do {
                        Frame frame = handler.unframe(data);
                        if (frame == null) {
                            buffer = data;
                            break;
                        } else {
                            for (Extension extension : negotiatedExtensions) {
                                if (extension instanceof ExtendedExtension) {
                                    try {
                                        frame = ((ExtendedExtension) extension).processIncoming(extensionContext, frame);
                                    } catch (Throwable t) {
                                        LOGGER.log(Level.FINE, String.format("Extension '%s' threw an exception during processIncoming method invocation: \"%s\".", extension.getName(), t.getMessage()), t);
                                    }
                                }
                            }

                            handler.process(frame, webSocket);
                        }
                    } while (true);
                }
            } catch (FramingException e) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
                webSocket.onClose(new CloseFrame(new CloseReason(CloseReason.CloseCodes.getCloseCode(e.getClosingCode()), e.getMessage())));
            } catch (Exception e) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
                webSocket.onClose(new CloseFrame(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, e.getMessage())));
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy