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

com.cisco.oss.foundation.http.AbstractHttpClient Maven / Gradle / Ivy

/*
 * Copyright 2015 Cisco Systems, Inc.
 *
 *  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 com.cisco.oss.foundation.http;

import com.cisco.oss.foundation.configuration.ConfigUtil;
import com.cisco.oss.foundation.configuration.FoundationConfigurationListener;
import com.cisco.oss.foundation.configuration.FoundationConfigurationListenerRegistry;
import com.cisco.oss.foundation.configuration.ConfigurationFactory;
import com.cisco.oss.foundation.loadbalancer.*;
import com.cisco.oss.foundation.monitoring.CommunicationInfo;
import com.cisco.oss.foundation.monitoring.MonitoringAgentFactory;
import com.cisco.oss.foundation.monitoring.serverconnection.ServerConnectionDetails;
import com.cisco.oss.foundation.string.utils.BoyerMoore;
import com.google.common.collect.Lists;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Abstract implementation common to all known HttpClient implementations
 * Created by Yair Ogen on 1/16/14.
 */
public abstract class AbstractHttpClient implements HttpClient {

    public static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractHttpClient.class);
    protected LoadBalancerStrategy loadBalancerStrategy = null;
    protected String apiName = "HTTP";
    protected boolean exposeStatisticsToMonitor = false;
    protected boolean autoEncodeUri = true;
    private static Map> boyersMap = new ConcurrentHashMap>();

    protected InternalServerProxyMetadata metadata;
    protected Configuration configuration;
    protected boolean enableLoadBalancing = true;
    protected boolean followRedirects = false;

    public AbstractHttpClient(String apiName, Configuration configuration, boolean enableLoadBalancing) {
        this(apiName, LoadBalancerStrategy.STRATEGY_TYPE.ROUND_ROBIN, configuration, enableLoadBalancing);
        exposeStatisticsToMonitor = getExposeStatisticsToMonitor();
        autoEncodeUri = metadata.isAutoEncodeUri();
        if(exposeStatisticsToMonitor){
            MonitoringAgentFactory.getInstance().register();
        }
    }

    public AbstractHttpClient(String apiName, LoadBalancerStrategy.STRATEGY_TYPE strategyType, Configuration configuration, boolean enableLoadBalancing) {
        this.apiName = apiName;
        this.enableLoadBalancing = enableLoadBalancing;
        if (configuration == null) {
            this.configuration = ConfigurationFactory.getConfiguration();
        } else {
            this.configuration = configuration;
        }
        exposeStatisticsToMonitor = getExposeStatisticsToMonitor();
        if (exposeStatisticsToMonitor) {
            MonitoringAgentFactory.getInstance().register(this.configuration);
        }

        metadata = loadServersMetadataConfiguration();

        loadBalancerStrategy = fromHighAvailabilityStrategyType(strategyType);
        createServerListFromConfig();
        autoEncodeUri = metadata.isAutoEncodeUri();
        followRedirects = metadata.isFollowRedirects();
        FoundationConfigurationListenerRegistry.addFoundationConfigurationListener(new LoadBalancerConfigurationListener());
    }

    private LoadBalancerStrategy fromHighAvailabilityStrategyType(LoadBalancerStrategy.STRATEGY_TYPE strategyType) {

        switch (strategyType) {
            case FAIL_OVER:
                return new FailOverStrategy(metadata.getServiceName(), metadata.isServiceDirectoryEnabled(), metadata.getWaitingTime(), apiName, metadata.getRetryDelay(), metadata.getNumberOfAttempts());
            case STICKY_ROUND_ROBIN:
                return new StickyRoundRobinStrategy(metadata.getServiceName(), metadata.isServiceDirectoryEnabled(), metadata.getWaitingTime(), apiName, metadata.getRetryDelay(), metadata.getNumberOfAttempts());
            default:
                return new RoundRobinStrategy(metadata.getServiceName(), metadata.isServiceDirectoryEnabled(), metadata.getWaitingTime(), apiName, metadata.getRetryDelay(), metadata.getNumberOfAttempts());
        }
    }

    @Override
    public R executeWithLoadBalancer(S request) {

        Throwable lastCaugtException = null;
        boolean successfullyInvoked = false;
        R result = null;
        // use a do while loop we need at least one attempt, to know if
        // the invocation was successful or not.
        // the stopping condition is when successfullyInvoked = true.
        do {

            InternalServerProxy serverProxy = null;

            try {
                readWriteLock.readLock().lock();
                serverProxy = loadBalancerStrategy.getServerProxy(request);
            } finally {
                readWriteLock.readLock().unlock();
            }

            if (serverProxy == null) {
                // server proxy will be null if the configuration was not
                // configured properly
                // or if all the servers are passivated.
                loadBalancerStrategy.handleNullserverProxy(apiName, lastCaugtException);
            }

            try {

                request = updateRequestUri(request, serverProxy);

                if (request.silentLogging) {
                    LOGGER.trace("sending request: {}-{}", request.getHttpMethod(), request.getUri());
                }else{
                    LOGGER.info("sending request: {}-{}", request.getHttpMethod(), request.getUri());
                }

                ServerConnectionDetails connectionDetails = new ServerConnectionDetails(apiName, "HTTP:" + request.getHttpMethod(), request.getUri().getHost(), -1, request.getUri().getPort());
                if (exposeStatisticsToMonitor) {
                    CommunicationInfo.getCommunicationInfo().transactionStarted(connectionDetails, getMonioringApiName(request));
                }
                result = executeDirect(request);

                if (request.silentLogging) {
                    LOGGER.trace("got response status: {} for request: {}", result.getStatus(), result.getRequestedURI());
                }else{
                    LOGGER.info("got response status: {} for request: {}", result.getStatus(), result.getRequestedURI());
                }
                if (exposeStatisticsToMonitor) {

                    int responseStatus = result.getStatus();

                    if (responseStatus >= 100 && responseStatus < 400) {
                        CommunicationInfo.getCommunicationInfo().transactionFinished(connectionDetails, getMonioringApiName(request), false, "");
                    } else {
                        CommunicationInfo.getCommunicationInfo().transactionFinished(connectionDetails, getMonioringApiName(request), true, responseStatus + "");
                    }
                }

                if(request.retryOnServerBusy){
                    if(result.getStatus() == 503){
                        lastCaugtException = loadBalancerStrategy.handleException(apiName, serverProxy, new ServerBusyException("server returned HTP 503 for client: " + apiName));
                    }else{
                        serverProxy.setCurrentNumberOfAttempts(0);
                        serverProxy.setFailedAttemptTimeStamp(0);
                        successfullyInvoked = true;
                    }

                }else{
                    serverProxy.setCurrentNumberOfAttempts(0);
                    serverProxy.setFailedAttemptTimeStamp(0);
                    successfullyInvoked = true;
                }


            } catch (Throwable e) {
                lastCaugtException = loadBalancerStrategy.handleException(apiName, serverProxy, e);
            }
        } while (!successfullyInvoked);

        return result;
    }

    protected S updateRequestUri(S request, InternalServerProxy serverProxy) {

        URI origUri = request.getUri();
        String host = serverProxy.getHost();
        String scheme = origUri.getScheme() == null ? (request.isHttpsEnabled() ? "https" : "http") : origUri.getScheme();

        int port = serverProxy.getPort();

        String urlPath = "";
        if (origUri.getRawPath() != null && origUri.getRawPath().startsWith("/")) {
            urlPath = origUri.getRawPath();
        } else {
            urlPath = "/" + origUri.getRawPath();
        }

        URI newURI = null;
        try {
            if(autoEncodeUri){
                String query = origUri.getQuery();
                newURI = new URI(scheme, origUri.getUserInfo(), host, port, urlPath, query, origUri.getFragment());
            }else{
                String query = origUri.getRawQuery();
                if (query != null){
                    newURI = new URI(scheme + "://" + host + ":" + port + urlPath + "?" + query);
                }else{
                    newURI = new URI(scheme + "://" + host + ":" + port + urlPath);
                }

            }
        } catch (URISyntaxException e) {
            throw new ClientException(e.toString());
        }

        S req = (S) request.replaceUri(newURI);
//        try {
//            req = (S) this.clone();
//        } catch (CloneNotSupportedException e) {
//            throw new IllegalArgumentException(e);
//        }
//        req.uri = newURI;
        return req;
    }

    @Override
    public void executeWithLoadBalancer(S request, ResponseCallback responseCallback) {
        execute(request, responseCallback, loadBalancerStrategy, apiName);
    }

//    @Override
//    public void setHighAvailabilityStrategy(LoadBalancerStrategy loadBalancerStrategy) {
//        this.loadBalancerStrategy = loadBalancerStrategy;
//        createServerListFromConfig();
//    }

    public abstract void execute(S request, ResponseCallback responseCallback, LoadBalancerStrategy loadBalancerStrategy, String apiName);

    @Override
    public R execute(S request) {
        if (enableLoadBalancing) {
            return executeWithLoadBalancer(request);
        } else {
            return executeDirect(request);
        }
    }

    protected abstract void configureClient();

    @Override
    public String getApiName() {
        return apiName;
    }

    private void createServerListFromConfig() {

        if (!metadata.isServiceDirectoryEnabled()) {

            List serversList = Lists.newCopyOnWriteArrayList();


            // based on the data collected from the config file - updates the server
            // list
            AbstractLoadBalancerStrategy.readWriteLock.writeLock().lock();
            try {
                serversList = updateServerListBasedOnConfig(serversList, metadata);
            } finally {
                AbstractLoadBalancerStrategy.readWriteLock.writeLock().unlock();
            }

            if (serversList.isEmpty()) {
                LOGGER.debug("No hosts defined for api: \"" + apiName + "\". Please check your config files!");
            }

            loadBalancerStrategy.setServerProxies(serversList);
        }

    }

    private InternalServerProxyMetadata loadServersMetadataConfiguration() {


        Configuration subset = configuration.subset(apiName);
        final Iterator keysIterator = subset.getKeys();

        // read default values
        int readTimeout = subset.getInt("http." + LoadBalancerConstants.READ_TIME_OUT, LoadBalancerConstants.DEFAULT_READ_TIMEOUT);
        int connectTimeout = subset.getInt("http." + LoadBalancerConstants.CONNECT_TIME_OUT, LoadBalancerConstants.DEFAULT_CONNECT_TIMEOUT);
        long waitingTime = subset.getLong("http." + LoadBalancerConstants.WAITING_TIME, LoadBalancerConstants.DEFAULT_WAITING_TIME);
        int numberOfAttempts = subset.getInt("http." + LoadBalancerConstants.NUMBER_OF_ATTEMPTS, LoadBalancerConstants.DEFAULT_NUMBER_OF_ATTEMPTS);
        long retryDelay = subset.getLong("http." + LoadBalancerConstants.RETRY_DELAY, LoadBalancerConstants.DEFAULT_RETRY_DELAY);

        long idleTimeout = subset.getLong("http." + LoadBalancerConstants.IDLE_TIME_OUT, LoadBalancerConstants.DEFAULT_IDLE_TIMEOUT);
        int maxConnectionsPerAddress = subset.getInt("http." + LoadBalancerConstants.MAX_CONNECTIONS_PER_ADDRESS, LoadBalancerConstants.DEFAULT_MAX_CONNECTIONS_PER_ADDRESS);
        int maxConnectionsTotal = subset.getInt("http." + LoadBalancerConstants.MAX_CONNECTIONS_TOTAL, LoadBalancerConstants.DEFAULT_MAX_CONNECTIONS_TOTAL);
        int maxQueueSizePerAddress = subset.getInt("http." + LoadBalancerConstants.MAX_QUEUE_PER_ADDRESS, LoadBalancerConstants.DEFAULT_MAX_QUEUE_PER_ADDRESS);
        boolean followRedirects = subset.getBoolean("http." + LoadBalancerConstants.FOLLOW_REDIRECTS, false);
        boolean disableCookies = subset.getBoolean("http." + LoadBalancerConstants.DISABLE_COOKIES, false);
        boolean autoCloseable = subset.getBoolean("http." + LoadBalancerConstants.AUTO_CLOSEABLE, true);
        boolean autoEncodeUri = subset.getBoolean("http." + LoadBalancerConstants.AUTO_ENCODE_URI, true);
        boolean staleConnectionCheckEnabled = subset.getBoolean("http." + LoadBalancerConstants.IS_STALE_CONN_CHECK_ENABLED, false);
        boolean serviceDirectoryEnabled = subset.getBoolean("http." + LoadBalancerConstants.SERVICE_DIRECTORY_IS_ENABLED, false);
        String serviceName = subset.getString("http." + LoadBalancerConstants.SERVICE_DIRECTORY_SERVICE_NAME, "UNKNOWN");

        String keyStorePath = subset.getString("http." + LoadBalancerConstants.KEYSTORE_PATH, "");
        String keyStorePassword = subset.getString("http." + LoadBalancerConstants.KEYSTORE_PASSWORD, "");
        String trustStorePath = subset.getString("http." + LoadBalancerConstants.TRUSTSTORE_PATH, "");
        String trustStorePassword = subset.getString("http." + LoadBalancerConstants.TRUSTSTORE_PASSWORD, "");


        final List keys = new ArrayList();

        while (keysIterator.hasNext()) {
            String key = keysIterator.next();
            keys.add(key);
        }

        Collections.sort(keys);

        List> hostAndPortPairs = new CopyOnWriteArrayList>();

        for (String key : keys) {

            if (key.contains(LoadBalancerConstants.HOST)) {

                String host = subset.getString(key);

                // trim the host name
                if (StringUtils.isNotEmpty(host)) {
                    host = host.trim();
                }
                final String portKey = key.replace(LoadBalancerConstants.HOST, LoadBalancerConstants.PORT);
                if (subset.containsKey(portKey)) {
                    int port = subset.getInt(portKey);
                    // save host and port for future creation of server list
                    hostAndPortPairs.add(Pair.of(host, port));
                }
            }

        }

        InternalServerProxyMetadata metadata = new InternalServerProxyMetadata(readTimeout, connectTimeout, idleTimeout, maxConnectionsPerAddress, maxConnectionsTotal, maxQueueSizePerAddress, waitingTime, numberOfAttempts, retryDelay, hostAndPortPairs, keyStorePath, keyStorePassword, trustStorePath, trustStorePassword, followRedirects, autoCloseable, staleConnectionCheckEnabled, disableCookies, serviceDirectoryEnabled, serviceName, autoEncodeUri);
//        metadata.getHostAndPortPairs().addAll(hostAndPortPairs);
//        metadata.setReadTimeout(readTimeout);
//        metadata.setConnectTimeout(connectTimeout);
//        metadata.setNumberOfRetries(numberOfAttempts);
//        metadata.setRetryDelay(retryDelay);
//        metadata.setWaitingTime(waitingTime);

        return metadata;

    }

    private InternalServerProxy createInternalServerProxy(final InternalServerProxyMetadata metadata, final String hostEntry, final int portEntry) {
        final InternalServerProxy internalServerProxy = new InternalServerProxy(metadata.getWaitingTime(), apiName);
        internalServerProxy.setRetryDelay(metadata.getRetryDelay());
        internalServerProxy.setMaxNumberOfAttempts(metadata.getNumberOfAttempts());
        internalServerProxy.setHost(hostEntry);
        internalServerProxy.setPort(portEntry);
        return internalServerProxy;
    }

    private List updateServerListBasedOnConfig(final List serversList, final InternalServerProxyMetadata metadata) {


        // iterate host and port pairs and create ad new servers to the server
        // list.
        for (Pair hostPort : metadata.getHostAndPortPairs()) {

            final String hostEntry = hostPort.getKey();
            final int portEntry = hostPort.getValue();

            final InternalServerProxy internalServerProxy = createInternalServerProxy(metadata, hostEntry, portEntry);
            serversList.add(internalServerProxy);
        }

        return serversList;
    }

    private boolean getExposeStatisticsToMonitor() {
        boolean monitor = configuration.getBoolean(apiName + ".http.exposeStatisticsToMonitor", true);
        return monitor;
    }

    protected String getMonioringApiName(S request) {

        if (apiName != null) {

            if (!boyersMap.containsKey(apiName)) {

                List boyers = populateBoyersList();
                if (boyers != null) {
                    boyersMap.put(apiName, boyers);
                }
            }

            List boyers = boyersMap.get(apiName);
            if (boyers != null && !boyers.isEmpty()) {
                String uri = request.getUri().toString();
                for (BoyerMoore boyerMoore : boyers) {
                    int match = boyerMoore.search(uri);
                    if (match >= 0) {
                        return boyerMoore.getPattern();
                    }
                }

            }

        }

        String path = request.getUri().getPath();
        int secondsSlashIndex = path.indexOf('/', 1);
        return secondsSlashIndex > 0 ? path.substring(0, secondsSlashIndex) : path;
    }

    private List populateBoyersList() {
        List boyers = new ArrayList();
        Map parseSimpleArrayAsMap = ConfigUtil.parseSimpleArrayAsMap(configuration, apiName + ".http.monitoringBaseUri");
        List keys = new ArrayList(parseSimpleArrayAsMap.keySet());
//		Collections.sort(keys);
        Collections.sort(keys, new Comparator() {
            // Overriding the compare method to sort the age
            public int compare(String str1, String str2) {
                return Integer.parseInt(str1) - Integer.parseInt(str2);
            }
        });
        for (String key : keys) {
            String baseUri = parseSimpleArrayAsMap.get(key);
            boyers.add(new BoyerMoore(baseUri));
        }
        return boyers;
    }

    /**
     * Listener for re-loading the internal list in case of dynamic configuration changes.
     */
    public class LoadBalancerConfigurationListener implements FoundationConfigurationListener {

        @Override
        public void configurationChanged() {

            LOGGER.debug("configuration has changed");

            List serverProxies = loadBalancerStrategy.getServerProxies();
            // List serverProxies = serverProxies2;
            InternalServerProxyMetadata metadata = loadServersMetadataConfiguration();

            List newServerProxies = Lists.newArrayListWithCapacity(serverProxies.size());

            List> hostAndPortPairs = metadata.getHostAndPortPairs();
            for (Pair hostPort : hostAndPortPairs) {

                String newHost = hostPort.getKey();
                int newPort = hostPort.getValue();

                boolean handled = false;

                for (InternalServerProxy serverProxy : serverProxies) {

                    String existingHost = serverProxy.getHost();
                    Integer existingPort = serverProxy.getPort();

                    if (existingHost.equals(newHost) && existingPort.equals(newPort)) {
                        handled = true;
                        newServerProxies.add(serverProxy);
                        break;
                    }
                }
                if (!handled) {

                    try {
                        final InternalServerProxy internalServerProxy = createInternalServerProxy(metadata, newHost, newPort);
                        newServerProxies.add(internalServerProxy);
                    } catch (Exception e) {
                        LOGGER.error("cannot update the internal server proxy list.", e);
                    }

                }
            }

            try {
                AbstractLoadBalancerStrategy.readWriteLock.writeLock().lock();
                if (loadBalancerStrategy.getServerProxies() == null) {
                    // loadBalancerStrategy was reloaded during
                    // configuration change.
                    // probably because there is a strategy parameter in
                    // ConfigurationFactory.getConfiguration().
                    // we need to reset loadBalancerStrategy parameters
                    loadBalancerStrategy.setServerProxies(newServerProxies);
                } else {
                    // just update the existing reference
                    loadBalancerStrategy.getServerProxies().clear();
                    loadBalancerStrategy.getServerProxies().addAll(newServerProxies);
                }
            } finally {
                AbstractLoadBalancerStrategy.readWriteLock.writeLock().unlock();
            }

        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy