reactor.ipc.netty.options.ClientOptions.java Source code

Java tutorial

Introduction

Here is the source code for reactor.ipc.netty.options.ClientOptions.java

Source

/*
 * Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package reactor.ipc.netty.options;

import java.net.InetSocketAddress;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.pool.ChannelPool;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.proxy.ProxyHandler;
import io.netty.handler.proxy.Socks4ProxyHandler;
import io.netty.handler.proxy.Socks5ProxyHandler;
import io.netty.handler.ssl.JdkSslContext;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.resolver.NoopAddressResolverGroup;
import io.netty.util.NetUtil;
import reactor.core.Exceptions;
import reactor.ipc.netty.resources.LoopResources;
import reactor.ipc.netty.resources.PoolResources;

/**
 * A client connector builder with low-level connection options including connection pooling and
 * proxy.
 *
 * @author Stephane Maldini
 */
public class ClientOptions extends NettyOptions<Bootstrap, ClientOptions> {

    /**
     * Create a client builder.
     *
     * @return a new client options builder
     */
    public static ClientOptions create() {
        return new ClientOptions();
    }

    static void defaultClientOptions(Bootstrap bootstrap) {
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000).option(ChannelOption.AUTO_READ, false)
                .option(ChannelOption.SO_RCVBUF, 1024 * 1024).option(ChannelOption.SO_SNDBUF, 1024 * 1024);
    }

    /**
     * Client connection pool selector
     */
    PoolResources poolResources;

    /**
     * Proxy options
     */
    String proxyUsername;
    Function<? super String, ? extends String> proxyPassword;
    Supplier<? extends InetSocketAddress> proxyAddress;
    Proxy proxyType;

    InternetProtocolFamily protocolFamily = null;
    Supplier<? extends InetSocketAddress> connectAddress = null;

    /**
     * Build a new {@link Bootstrap}
     */
    protected ClientOptions() {
        this(new Bootstrap());
    }

    /**
     * Apply common option via super constructor then apply
     * {@link #defaultClientOptions(Bootstrap)}
     * to the passed bootstrap.
     *
     * @param bootstrap the bootstrap reference to use
     */
    protected ClientOptions(Bootstrap bootstrap) {
        super(bootstrap);
        defaultClientOptions(bootstrap);
    }

    /**
     * Deep-copy all references from the passed options into this new
     * {@link NettyOptions} instance.
     *
     * @param options the source options to duplicate
     */
    protected ClientOptions(ClientOptions options) {
        super(options);
        this.proxyUsername = options.proxyUsername;
        this.proxyPassword = options.proxyPassword;
        this.proxyAddress = options.proxyAddress;
        this.proxyType = options.proxyType;
        this.connectAddress = options.connectAddress;
        this.poolResources = options.poolResources;
        this.protocolFamily = options.protocolFamily;
    }

    /**
     * The localhost port to which this client should connect.
     *
     * @param port The port to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions connect(int port) {
        return connect(new InetSocketAddress(NetUtil.LOCALHOST.getHostAddress(), port));
    }

    /**
     * The host and port to which this client should connect.
     *
     * @param host The host to connect to.
     * @param port The port to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions connect(@Nonnull String host, int port) {
        return connect(InetSocketAddress.createUnresolved(host, port));
    }

    /**
     * The address to which this client should connect.
     *
     * @param connectAddress The address to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions connect(@Nonnull InetSocketAddress connectAddress) {
        return connect(() -> connectAddress);
    }

    /**
     * The address to which this client should connect.
     *
     * @param connectAddress The address to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions connect(@Nonnull Supplier<? extends InetSocketAddress> connectAddress) {
        this.connectAddress = Objects.requireNonNull(connectAddress, "connectAddress");
        return this;
    }

    /**
     * Disable current {@link #poolResources}
     *
     * @return {@code this}
     */
    public ClientOptions disablePool() {
        this.poolResources = null;
        return this;
    }

    @Override
    public ClientOptions duplicate() {
        return new ClientOptions(this);
    }

    @Override
    public Bootstrap get() {
        return get(null);
    }

    /**
     * Return a new {@link Bootstrap} given an optional address or the current
     * {@link #connect(Supplier)} resolved.
     *
     * @param address an optional address
     *
     * @return a new {@link Bootstrap}
     */
    public final Bootstrap get(InetSocketAddress address) {
        Bootstrap b = super.get();
        InetSocketAddress adr;
        if (address != null) {
            adr = address;
        } else if (connectAddress != null) {
            adr = connectAddress.get();
        } else {
            adr = null;
        }

        if (adr != null) {
            if (useDatagramChannel()) {
                b.localAddress(adr);
            } else {
                b.remoteAddress(adr);
            }
        }
        groupAndChannel(b);
        return b;
    }

    /**
     * Select a channel pool from the given address.
     *
     * @return an eventual {@link ChannelPool}
     */
    public final ChannelPool getPool() {
        return getPool(null);
    }

    /**
     * Select a channel pool from the given address.
     *
     * @param address the optional address to use
     *
     * @return an eventual {@link ChannelPool}
     */
    public final ChannelPool getPool(InetSocketAddress address) {
        if (poolResources == null) {
            return null;
        }
        address = address == null && connectAddress != null ? connectAddress.get() : address;
        return poolResources.selectOrCreate(address, this);
    }

    /**
     * Return a new eventual {@link ProxyHandler}
     *
     * @return a new eventual {@link ProxyHandler}
     */
    public final ProxyHandler getProxyHandler() {
        if (proxyType == null) {
            return null;
        }
        InetSocketAddress proxyAddr = proxyAddress.get();
        String username = proxyUsername;
        String password = username != null && proxyPassword != null ? proxyPassword.apply(username) : null;

        switch (proxyType) {
        case HTTP:
            return username != null && password != null ? new HttpProxyHandler(proxyAddr, username, password)
                    : new HttpProxyHandler(proxyAddr);
        case SOCKS4:
            return username != null ? new Socks4ProxyHandler(proxyAddr, username)
                    : new Socks4ProxyHandler(proxyAddr);
        case SOCKS5:
            return username != null && password != null ? new Socks5ProxyHandler(proxyAddr, username, password)
                    : new Socks5ProxyHandler(proxyAddr);
        }
        throw new IllegalArgumentException("Proxy type unsupported : " + proxyType);
    }

    /**
     * Resolve the latest {@link #connect} address
     *
     * @return the resolved address if any
     */
    public final InetSocketAddress getRemoteAddress() {
        return null != connectAddress ? connectAddress.get() : null;
    }

    /**
     * Configures the {@link ChannelPool} selector for the socket. Will effectively
     * enable client connection-pooling.
     *
     * @param poolResources the {@link PoolResources} given
     * an {@link InetSocketAddress}
     *
     * @return {@code this}
     */
    public ClientOptions poolResources(PoolResources poolResources) {
        Objects.requireNonNull(poolResources, "poolResources");
        this.poolResources = poolResources;
        return this;
    }

    /**
     * Configures the version family for the socket.
     *
     * @param protocolFamily the version family for the socket, or null for the system
     * default family
     *
     * @return {@code this}
     */
    public ClientOptions protocolFamily(InternetProtocolFamily protocolFamily) {
        Objects.requireNonNull(protocolFamily, "protocolFamily");
        this.protocolFamily = protocolFamily;
        return this;
    }

    /**
     * The host and port to which this client should connect.
     *
     * @param host The host to connect to.
     * @param port The port to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions proxy(@Nonnull Proxy type, @Nonnull String host, int port, @Nullable String username,
            @Nullable Function<? super String, ? extends String> password) {
        return proxy(type, InetSocketAddress.createUnresolved(host, port), username, password);
    }

    /**
     * The host and port to which this client should connect.
     *
     * @param host The host to connect to.
     * @param port The port to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions proxy(@Nonnull Proxy type, @Nonnull String host, int port) {
        return proxy(type, InetSocketAddress.createUnresolved(host, port));
    }

    /**
     * The address to which this client should connect.
     *
     * @param connectAddress The address to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions proxy(@Nonnull Proxy type, @Nonnull InetSocketAddress connectAddress) {
        return proxy(type, connectAddress, null, null);
    }

    /**
     * The address to which this client should connect.
     *
     * @param connectAddress The address to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions proxy(@Nonnull Proxy type, @Nonnull InetSocketAddress connectAddress,
            @Nullable String username, @Nullable Function<? super String, ? extends String> password) {
        return proxy(type,
                connectAddress.isUnresolved()
                        ? () -> new InetSocketAddress(connectAddress.getHostName(), connectAddress.getPort())
                        : () -> connectAddress,
                username, password);
    }

    /**
     * The address to which this client should connect.
     *
     * @param connectAddress The address to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions proxy(@Nonnull Proxy type, @Nonnull Supplier<? extends InetSocketAddress> connectAddress) {
        return proxy(type, connectAddress, null, null);
    }

    /**
     * The address to which this client should connect.
     *
     * @param connectAddress The address to connect to.
     *
     * @return {@literal this}
     */
    public ClientOptions proxy(@Nonnull Proxy type, @Nonnull Supplier<? extends InetSocketAddress> connectAddress,
            @Nullable String username, @Nullable Function<? super String, ? extends String> password) {
        this.proxyUsername = username;
        this.proxyPassword = password;
        this.proxyAddress = Objects.requireNonNull(connectAddress, "addressSupplier");
        this.proxyType = Objects.requireNonNull(type, "proxyType");
        bootstrapTemplate.resolver(NoopAddressResolverGroup.INSTANCE);
        return this;
    }

    /**
     * Enable default sslContext support
     *
     * @return this {@link ClientOptions}
     */
    public ClientOptions sslSupport() {
        return sslSupport(c -> {
        });
    }

    /**
     * Enable default sslContext support and enable further customization via the passed
     * configurator. The builder will then produce the {@link SslContext} to be passed to
     * {@link #sslContext(SslContext)}.
     *
     * @param configurator builder callback for further customization.
     *
     * @return this {@link ClientOptions}
     */
    public ClientOptions sslSupport(Consumer<? super SslContextBuilder> configurator) {
        Objects.requireNonNull(configurator, "configurator");
        try {
            SslContextBuilder builder = SslContextBuilder.forClient();
            configurator.accept(builder);
            return sslContext(builder.build());
        } catch (Exception sslException) {
            throw Exceptions.bubble(sslException);
        }
    }

    //

    /**
     * Proxy Type
     */
    public enum Proxy {
        HTTP, SOCKS4, SOCKS5

    }

    /**
     * Return true if {@link io.netty.channel.socket.DatagramChannel} should be used
     *
     * @return true if {@link io.netty.channel.socket.DatagramChannel} should be used
     */
    protected boolean useDatagramChannel() {
        return false;
    }

    /**
     * Return true if proxy options have been set
     *
     * @return true if proxy options have been set
     */
    protected boolean useProxy() {
        return proxyType != null;
    }

    final void groupAndChannel(Bootstrap bootstrap) {
        LoopResources loops = Objects.requireNonNull(this.loopResources, "loopResources");

        boolean useNative = protocolFamily == null && preferNative && !(sslContext instanceof JdkSslContext);
        EventLoopGroup elg = loops.onClient(useNative);
        bootstrap.group(elg);

        if (useDatagramChannel()) {
            if (useNative) {
                bootstrap.channel(loops.onDatagramChannel(elg));
            } else {
                bootstrap.channelFactory(() -> new NioDatagramChannel(protocolFamily));
            }
        } else {
            bootstrap.channel(loops.onChannel(elg));
        }
    }

}