Package org.glassfish.tyrus.container.jdk.client

Source Code of org.glassfish.tyrus.container.jdk.client.JdkClientContainer

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 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.container.jdk.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.websocket.ClientEndpointConfig;
import javax.websocket.DeploymentException;

import org.glassfish.tyrus.client.ClientManager;
import org.glassfish.tyrus.client.ClientProperties;
import org.glassfish.tyrus.client.SslContextConfigurator;
import org.glassfish.tyrus.client.SslEngineConfigurator;
import org.glassfish.tyrus.client.ThreadPoolConfig;
import org.glassfish.tyrus.core.Base64Utils;
import org.glassfish.tyrus.core.Utils;
import org.glassfish.tyrus.spi.ClientContainer;
import org.glassfish.tyrus.spi.ClientEngine;

/**
* {@link org.glassfish.tyrus.spi.ClientContainer} implementation based on Java 7 NIO API.
*
* @author Petr Janouch (petr.janouch at oracle.com)
*/
public class JdkClientContainer implements ClientContainer {

    /**
     * Input buffer that is used by {@link org.glassfish.tyrus.container.jdk.client.TransportFilter} when SSL is turned on.
     * The size cannot be smaller than a maximal size of a SSL packet, which is 16kB for payload + header, because
     * {@link org.glassfish.tyrus.container.jdk.client.SslFilter} does not have its own buffer for buffering incoming
     * data and therefore the entire SSL packet must fit into {@link org.glassfish.tyrus.container.jdk.client.TransportFilter}
     * input buffer.
     */
    private static final int SSL_INPUT_BUFFER_SIZE = 17_000;
    /**
     * Input buffer that is used by {@link org.glassfish.tyrus.container.jdk.client.TransportFilter} when SSL is not turned on.
     */
    private static final int INPUT_BUFFER_SIZE = 2048;
    private static final Logger LOGGER = Logger.getLogger(JdkClientContainer.class.getName());

    private final List<Proxy> proxies = new ArrayList<>();

    @Override
    public void openClientSocket(final String url, final ClientEndpointConfig cec, final Map<String, Object> properties, final ClientEngine clientEngine) throws DeploymentException, IOException {
        final URI uri;
        try {
            uri = new URI(url);
        } catch (URISyntaxException e) {
            throw new DeploymentException("Invalid URI.", e);
        }

        final boolean secure = uri.getScheme().equalsIgnoreCase("wss");

        ThreadPoolConfig threadPoolConfig = Utils.getProperty(properties, ClientProperties.WORKER_THREAD_POOL_CONFIG, ThreadPoolConfig.class);
        if (threadPoolConfig == null) {
            threadPoolConfig = ThreadPoolConfig.defaultConfig();
        }
        // weblogic.websocket.client.max-aio-threads has priority over what is in thread pool config
        String wlsMaxThreadsStr = System.getProperty(ClientManager.WLS_MAX_THREADS);
        if (wlsMaxThreadsStr != null) {
            try {
                int wlsMaxThreads = Integer.parseInt(wlsMaxThreadsStr);
                threadPoolConfig.setMaxPoolSize(wlsMaxThreads);
            } catch (Exception e) {
                LOGGER.log(Level.CONFIG, String.format("Invalid type of configuration property of %s , %s cannot be cast to Integer", ClientManager.WLS_MAX_THREADS, wlsMaxThreadsStr));
            }
        }

        final Integer containerIdleTimeout = Utils.getProperty(properties, ClientProperties.SHARED_CONTAINER_IDLE_TIMEOUT, Integer.class);

        processProxy(properties, uri);

        final ThreadPoolConfig finalThreadPoolConfig = threadPoolConfig;
        final Callable<Void> jdkConnector = new Callable<Void>() {

            @Override
            public Void call() throws Exception {
                final TransportFilter transportFilter;
                final ClientFilter clientFilter = createClientFilter(properties, clientEngine, uri, this);
                final TaskQueueFilter writeQueue = createTaskQueueFilter(clientFilter);

                if (secure) {
                    SslFilter sslFilter = createSslFilter(cec, properties, writeQueue);
                    transportFilter = createTransportFilter(sslFilter, SSL_INPUT_BUFFER_SIZE, finalThreadPoolConfig, containerIdleTimeout);
                } else {
                    transportFilter = createTransportFilter(writeQueue, INPUT_BUFFER_SIZE, finalThreadPoolConfig, containerIdleTimeout);
                }

                _connect(clientFilter, transportFilter, uri);

                return null;
            }
        };

        try {
            jdkConnector.call();
        } catch (Exception e) {
            if (e instanceof DeploymentException) {
                throw (DeploymentException) e;
            }

            if (e instanceof IOException) {
                throw (IOException) e;
            }

            throw new DeploymentException(e.getMessage(), e);
        }
    }

    private SslFilter createSslFilter(ClientEndpointConfig cec, Map<String, Object> properties, TaskQueueFilter writeQueue) {
        Object sslEngineConfiguratorObject = properties.get(ClientProperties.SSL_ENGINE_CONFIGURATOR);

        SslFilter sslFilter = null;

        if (sslEngineConfiguratorObject != null) {
            // property is set, we need to figure out whether new or deprecated one is used and act accordingly.
            if (sslEngineConfiguratorObject instanceof SslEngineConfigurator) {
                sslFilter = new SslFilter(writeQueue, (SslEngineConfigurator) sslEngineConfiguratorObject);
            } else if (sslEngineConfiguratorObject instanceof org.glassfish.tyrus.container.jdk.client.SslEngineConfigurator) {
                sslFilter = new SslFilter(writeQueue, (org.glassfish.tyrus.container.jdk.client.SslEngineConfigurator) sslEngineConfiguratorObject);
            } else {
                LOGGER.log(Level.WARNING, "Invalid '" + ClientProperties.SSL_ENGINE_CONFIGURATOR + "' property value: " + sslEngineConfiguratorObject +
                        ". Using system defaults.");
            }
        }

        // if we are trying to access "wss" scheme and we don't have sslEngineConfigurator instance
        // we should try to create ssl connection using JVM properties.
        if (sslFilter == null) {
            SslContextConfigurator defaultConfig = new SslContextConfigurator();
            defaultConfig.retrieve(System.getProperties());

            String wlsSslTrustStore = (String) cec.getUserProperties().get(ClientManager.WLS_SSL_TRUSTSTORE_PROPERTY);
            String wlsSslTrustStorePassword = (String) cec.getUserProperties().get(ClientManager.WLS_SSL_TRUSTSTORE_PWD_PROPERTY);

            if (wlsSslTrustStore != null) {
                defaultConfig.setTrustStoreFile(wlsSslTrustStore);

                if (wlsSslTrustStorePassword != null) {
                    defaultConfig.setTrustStorePassword(wlsSslTrustStorePassword);
                }
            }

            // client mode = true, needClientAuth = false, wantClientAuth = false
            SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(defaultConfig, true, false, false);
            String wlsSslProtocols = (String) cec.getUserProperties().get(ClientManager.WLS_SSL_PROTOCOLS_PROPERTY);
            if (wlsSslProtocols != null) {
                sslEngineConfigurator.setEnabledProtocols(wlsSslProtocols.split(","));
            }
            sslFilter = new SslFilter(writeQueue, sslEngineConfigurator);
        }
        return sslFilter;
    }

    private TransportFilter createTransportFilter(Filter filter, int sslInputBufferSize, ThreadPoolConfig threadPoolConfig, int containerIdleTimeout) {
        return new TransportFilter(filter, sslInputBufferSize, threadPoolConfig, containerIdleTimeout);
    }

    private TaskQueueFilter createTaskQueueFilter(ClientFilter clientFilter) {
        return new TaskQueueFilter(clientFilter);
    }

    private ClientFilter createClientFilter(Map<String, Object> properties, ClientEngine clientEngine, URI uri, Callable<Void> jdkConnector) throws DeploymentException {
        return new ClientFilter(clientEngine, uri, getProxyHeaders(properties), jdkConnector);
    }

    private SocketAddress getServerAddress(URI uri) {
        int port = uri.getPort();
        if (port == -1) {
            String scheme = uri.getScheme();
            assert scheme != null && (scheme.equals("ws") || scheme.equals("wss"));
            if (scheme.equals("ws")) {
                port = 80;
            } else if (scheme.equals("wss")) {
                port = 443;
            }
        }
        return new InetSocketAddress(uri.getHost(), port);
    }

    private void _connect(ClientFilter clientFilter, TransportFilter transportFilter, URI uri) throws DeploymentException, IOException {
        for (Proxy proxy : proxies) {
            // Proxy.Type.DIRECT is always present and is always last.
            if (proxy.type() == Proxy.Type.DIRECT) {
                clientFilter.setProxy(false);
                transportFilter.connect(getServerAddress(uri), null);
                return;
            }
            clientFilter.setProxy(true);
            LOGGER.log(Level.CONFIG, String.format("Connecting to '%s' via proxy '%s'.", uri, proxy));
            // default ProxySelector always returns proxies with unresolved addresses.
            SocketAddress proxyAddress = proxy.address();
            if (proxyAddress instanceof InetSocketAddress) {
                InetSocketAddress inetSocketAddress = (InetSocketAddress) proxyAddress;
                if (inetSocketAddress.isUnresolved()) {
                    // resolve the address.
                    proxyAddress = new InetSocketAddress(inetSocketAddress.getHostName(), inetSocketAddress.getPort());
                }
            }
            final AtomicBoolean success = new AtomicBoolean(false);
            final CountDownLatch connectLatch = new CountDownLatch(1);
            try {
                final SocketAddress finalProxyAddress = proxyAddress;
                transportFilter.connect(proxyAddress, new CompletionHandler<Void, Void>() {
                    @Override
                    public void completed(Void result, Void attachment) {
                        success.set(true);
                        connectLatch.countDown();
                    }

                    @Override
                    public void failed(Throwable exc, Void attachment) {
                        LOGGER.log(Level.FINE, "Connecting to " + finalProxyAddress + " failed", exc);
                        connectLatch.countDown();
                    }
                });
                connectLatch.await();
                if (success.get()) {
                    return;
                }
            } catch (IOException | InterruptedException e) {
                LOGGER.log(Level.FINE, "Connecting to " + proxyAddress + " failed", e);
            }
        }
    }

    private void processProxy(Map<String, Object> properties, URI uri) throws DeploymentException {
        String wlsProxyHost = null;
        Integer wlsProxyPort = null;

        Object value = properties.get(ClientManager.WLS_PROXY_HOST);
        if (value != null) {
            if (value instanceof String) {
                wlsProxyHost = (String) value;
            } else {
                throw new DeploymentException(ClientManager.WLS_PROXY_HOST + " only accept String values.");
            }
        }

        value = properties.get(ClientManager.WLS_PROXY_PORT);
        if (value != null) {
            if (value instanceof Integer) {
                wlsProxyPort = (Integer) value;
            } else {
                throw new DeploymentException(ClientManager.WLS_PROXY_PORT + " only accept Integer values.");
            }
        }

        if (wlsProxyHost != null) {
            proxies.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(wlsProxyHost, wlsProxyPort == null ? 80 : wlsProxyPort)));
        } else {
            Object proxyString = properties.get(ClientProperties.PROXY_URI);
            try {
                URI proxyUri;
                if (proxyString != null) {
                    proxyUri = new URI(proxyString.toString());
                    if (proxyUri.getHost() == null) {
                        LOGGER.log(Level.WARNING, String.format("Invalid proxy '%s'.", proxyString));
                    } else {
                        // proxy set via properties
                        int proxyPort = proxyUri.getPort() == -1 ? 80 : proxyUri.getPort();
                        proxies.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyUri.getHost(), proxyPort)));
                    }
                }
            } catch (URISyntaxException e) {
                LOGGER.log(Level.WARNING, String.format("Invalid proxy '%s'.", proxyString), e);
            }
        }

        // ProxySelector
        final ProxySelector proxySelector = ProxySelector.getDefault();

        // see WebSocket Protocol RFC, chapter 4.1.3: http://tools.ietf.org/html/rfc6455#section-4.1
        addProxies(proxySelector, uri, "socket", proxies);
        addProxies(proxySelector, uri, "https", proxies);
        addProxies(proxySelector, uri, "http", proxies);
        proxies.add(Proxy.NO_PROXY);
    }

    private void addProxies(ProxySelector proxySelector, URI uri, String scheme, List<Proxy> proxies) {
        for (Proxy p : proxySelector.select(getProxyUri(uri, scheme))) {
            switch (p.type()) {
                case HTTP:
                    LOGGER.log(Level.FINE, String.format("Found proxy: '%s'", p));
                    proxies.add(p);
                    break;
                case SOCKS:
                    LOGGER.log(Level.INFO, String.format("Socks proxy is not supported, please file new issue at https://java.net/jira/browse/TYRUS. Proxy '%s' will be ignored.", p));
                    break;
                default:
                    break;
            }
        }
    }

    private URI getProxyUri(URI wsUri, String scheme) {
        try {
            return new URI(scheme, wsUri.getUserInfo(), wsUri.getHost(), wsUri.getPort(), wsUri.getPath(), wsUri.getQuery(), wsUri.getFragment());
        } catch (URISyntaxException e) {
            LOGGER.log(Level.WARNING, String.format("Exception during generating proxy URI '%s'", wsUri), e);
            return wsUri;
        }
    }

    private Map<String, String> getProxyHeaders(Map<String, Object> properties) throws DeploymentException {
        //noinspection unchecked
        Map<String, String> proxyHeaders = Utils.getProperty(properties, ClientProperties.PROXY_HEADERS, Map.class);

        String wlsProxyUsername = null;
        String wlsProxyPassword = null;

        Object value = properties.get(ClientManager.WLS_PROXY_USERNAME);
        if (value != null) {
            if (value instanceof String) {
                wlsProxyUsername = (String) value;
            } else {
                throw new DeploymentException(ClientManager.WLS_PROXY_USERNAME + " only accept String values.");
            }
        }

        value = properties.get(ClientManager.WLS_PROXY_PASSWORD);
        if (value != null) {
            if (value instanceof String) {
                wlsProxyPassword = (String) value;
            } else {
                throw new DeploymentException(ClientManager.WLS_PROXY_PASSWORD + " only accept String values.");
            }
        }

        if (proxyHeaders == null) {
            if (wlsProxyUsername != null && wlsProxyPassword != null) {
                proxyHeaders = new HashMap<String, String>();
                proxyHeaders.put("Proxy-Authorization", "Basic " +
                        Base64Utils.encodeToString((wlsProxyUsername + ":" + wlsProxyPassword).getBytes(Charset.forName("UTF-8")), false));
            }
        } else {
            boolean proxyAuthPresent = false;
            for (Map.Entry<String, String> entry : proxyHeaders.entrySet()) {
                if (entry.getKey().equalsIgnoreCase("Proxy-Authorization")) {
                    proxyAuthPresent = true;
                }
            }

            // if (proxyAuthPresent == true) then do nothing, proxy authorization header is already added.
            if (!proxyAuthPresent && wlsProxyUsername != null && wlsProxyPassword != null) {
                proxyHeaders.put("Proxy-Authorization", "Basic " +
                        Base64Utils.encodeToString((wlsProxyUsername + ":" + wlsProxyPassword).getBytes(Charset.forName("UTF-8")), false));
            }
        }
        return proxyHeaders;
    }

}
TOP

Related Classes of org.glassfish.tyrus.container.jdk.client.JdkClientContainer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.