DeferredHttpProxyProvider.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.http.netty.implementation;

import com.azure.core.http.ProxyOptions;
import com.azure.core.util.AuthorizationChallengeHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import reactor.netty.ConnectionObserver;
import reactor.netty.NettyPipeline;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
 * This class defers supplying a channel pipeline with a proxy handler.
 */
public class DeferredHttpProxyProvider implements Function<Bootstrap, BiConsumer<ConnectionObserver, Channel>> {
    private final AuthorizationChallengeHandler challengeHandler;
    private final AtomicReference<ChallengeHolder> proxyChallengeHolderReference;
    private final InetSocketAddress proxyAddress;
    private final String username;
    private final String password;
    private final String nonProxyHosts;

    private final Pattern nonProxyHostsPattern;

    public DeferredHttpProxyProvider(AuthorizationChallengeHandler challengeHandler,
        AtomicReference<ChallengeHolder> proxyChallengeHolderReference, ProxyOptions proxyOptions) {
        this.challengeHandler = challengeHandler;
        this.proxyChallengeHolderReference = proxyChallengeHolderReference;
        this.proxyAddress = proxyOptions.getAddress();
        this.username = proxyOptions.getUsername();
        this.password = proxyOptions.getPassword();
        this.nonProxyHosts = proxyOptions.getNonProxyHosts();

        this.nonProxyHostsPattern = (nonProxyHosts == null)
            ? null
            : Pattern.compile(nonProxyHosts, Pattern.CASE_INSENSITIVE);
    }

    @Override
    public BiConsumer<ConnectionObserver, Channel> apply(Bootstrap bootstrap) {
        return ((connectionObserver, channel) -> {
            if (shouldApplyProxy(bootstrap.config().remoteAddress())) {
                channel.pipeline()
                    .addFirst(NettyPipeline.ProxyHandler, new HttpProxyHandler(proxyAddress, challengeHandler,
                        proxyChallengeHolderReference))
                    .addLast("azure.proxy.exceptionHandler", new HttpProxyExceptionHandler());
            }
        });
    }

    private boolean shouldApplyProxy(SocketAddress socketAddress) {
        if (nonProxyHostsPattern == null) {
            return true;
        }

        if (!(socketAddress instanceof  InetSocketAddress)) {
            return true;
        }

        InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;

        return !nonProxyHostsPattern.matcher(inetSocketAddress.getHostName()).matches();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (!(o instanceof DeferredHttpProxyProvider)) {
            return false;
        }

        DeferredHttpProxyProvider other = (DeferredHttpProxyProvider) o;

        return Objects.equals(username, other.username)
            && Objects.equals(password, other.password)
            && Objects.equals(proxyAddress, other.proxyAddress)
            && Objects.equals(nonProxyHosts, other.nonProxyHosts);
    }

    @Override
    public int hashCode() {
        return Objects.hash(proxyAddress, password, proxyAddress, nonProxyHosts);
    }
}