com.cisco.oss.foundation.http.netlifx.netty.NettyNetflixHttpClient.java Source code

Java tutorial

Introduction

Here is the source code for com.cisco.oss.foundation.http.netlifx.netty.NettyNetflixHttpClient.java

Source

/*
 * 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.netlifx.netty;

import com.cisco.oss.foundation.configuration.ConfigurationFactory;
import com.cisco.oss.foundation.flowcontext.FlowContextFactory;
import com.cisco.oss.foundation.http.*;
import com.cisco.oss.foundation.loadbalancer.LoadBalancerConstants;
import com.cisco.oss.foundation.loadbalancer.LoadBalancerStrategy;
import com.google.common.base.Joiner;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.MyDataCenterInstanceConfig;
import com.netflix.client.RetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.config.IClientConfigKey;
import com.netflix.config.ConfigurationManager;
import com.netflix.discovery.DefaultEurekaClientConfig;
import com.netflix.discovery.DiscoveryManager;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import com.netflix.ribbon.transport.netty.RibbonTransport;
import com.netflix.ribbon.transport.netty.http.LoadBalancingHttpClient;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpMethod;
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Subscriber;

import javax.net.ssl.HostnameVerifier;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Created by Yair Ogen (yaogen) on 13/12/2015.
 */
class NettyNetflixHttpClient implements HttpClient<HttpRequest, NettyNetflixHttpResponse> {

    static {
        if (!ConfigurationManager.isConfigurationInstalled()) {
            ConfigurationManager.install((AbstractConfiguration) ConfigurationFactory.getConfiguration());
        }
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(NettyNetflixHttpClient.class);
    private HostnameVerifier hostnameVerifier;
    protected InternalServerProxyMetadata metadata = null;
    protected boolean followRedirects = false;
    protected boolean autoEncodeUri = true;
    private IClientConfig clientConfig = null;
    private Joiner joiner = Joiner.on(",").skipNulls();
    private String apiName = "HTTP";
    protected boolean enableLoadBalancing = true;
    BaseLoadBalancer loadBalancer = new DynamicServerListLoadBalancer<DiscoveryEnabledServer>();
    LoadBalancingHttpClient<ByteBuf, ByteBuf> httpClient = null;
    private RetryHandler retryHandler = null;
    private boolean startEurekaClient = true;

    public NettyNetflixHttpClient(String apiName, Configuration configuration, boolean enableLoadBalancing,
            HostnameVerifier hostnameVerifier) {
        this.hostnameVerifier = hostnameVerifier;
        this.apiName = apiName;
        this.enableLoadBalancing = enableLoadBalancing;
        this.metadata = loadServersMetadataConfiguration();
        configureClient();
    }

    public boolean isAutoCloseable() {
        return autoCloseable;
    }

    private boolean autoCloseable = true;

    @Override
    public NettyNetflixHttpResponse execute(HttpRequest request) {

        try {
            if (enableLoadBalancing) {
                return executeWithLoadBalancer(request);
            } else {
                return (NettyNetflixHttpResponse) executeDirect(request);
            }

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

    public NettyNetflixHttpResponse executeDirect(HttpRequest request) {
        HttpClientRequest<ByteBuf> httpRequest = buildNetflixHttpRequest(request, joiner);
        rx.Observable<HttpClientResponse<ByteBuf>> responseObservable = httpClient.submit(httpRequest, retryHandler,
                clientConfig);
        HttpClientResponse<ByteBuf> httpResponse = responseObservable.toBlocking().first();
        ByteBuf content = httpResponse.getContent().doOnNext(ByteBuf::retain).toBlocking().first();
        return new NettyNetflixHttpResponse(httpResponse, content);
    }

    @Override
    public NettyNetflixHttpResponse executeWithLoadBalancer(HttpRequest request) {
        return executeDirect(request);

    }

    @Override
    public void executeWithLoadBalancer(HttpRequest request,
            final ResponseCallback<NettyNetflixHttpResponse> responseCallback) {
        HttpClientRequest<ByteBuf> httpRequest = buildNetflixHttpRequest(request, joiner);
        rx.Observable<HttpClientResponse<ByteBuf>> responseObservable = httpClient.submit(httpRequest, retryHandler,
                clientConfig);

        responseObservable.subscribe(new Subscriber<HttpClientResponse<ByteBuf>>() {

            @Override
            public void onCompleted() {
                LOGGER.debug("Http Response Completed");
            }

            @Override
            public void onError(Throwable e) {
                LOGGER.error("error serving request: {}", e, e);
                responseCallback.failed(e);
            }

            @Override
            public void onNext(HttpClientResponse<ByteBuf> httpClientResponse) {

                httpClientResponse.getContent().subscribe(new Subscriber<ByteBuf>() {

                    private ByteBuf responseContent = null;

                    @Override
                    public void onCompleted() {
                        responseCallback
                                .completed(new NettyNetflixHttpResponse(httpClientResponse, responseContent));
                    }

                    @Override
                    public void onError(Throwable e) {
                        LOGGER.error("error serving request: {}", e, e);
                        responseCallback.failed(e);
                    }

                    @Override
                    public void onNext(ByteBuf content) {
                        content.retain();
                        this.responseContent = content;
                    }
                });
            }
        });

    }

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

    protected void configureClient() {

        clientConfig = new DefaultClientConfigImpl();
        clientConfig.loadProperties(getApiName());

        if (DiscoveryManager.getInstance().getDiscoveryClient() == null && startEurekaClient) {
            EurekaInstanceConfig eurekaInstanceConfig = new MyDataCenterInstanceConfig(getApiName());
            EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig(getApiName() + ".");
            DiscoveryManager.getInstance().initComponent(eurekaInstanceConfig, eurekaClientConfig);
        }

        loadBalancer.initWithNiwsConfig(clientConfig);

        httpClient = RibbonTransport.newHttpClient(loadBalancer, clientConfig);

        retryHandler = new NettyNetflixRetryHandler(metadata);

        boolean addSslSupport = StringUtils.isNotEmpty(metadata.getKeyStorePath())
                && StringUtils.isNotEmpty(metadata.getKeyStorePassword());

        boolean addTrustSupport = StringUtils.isNotEmpty(metadata.getTrustStorePath())
                && StringUtils.isNotEmpty(metadata.getTrustStorePassword());

        autoCloseable = metadata.isAutoCloseable();

        autoEncodeUri = metadata.isAutoEncodeUri();
        followRedirects = metadata.isFollowRedirects();

    }

    private URI buildUri(HttpRequest request, Joiner joiner) {
        URI requestUri = request.getUri();

        Map<String, Collection<String>> queryParams = request.getQueryParams();
        if (queryParams != null && !queryParams.isEmpty()) {
            URIBuilder uriBuilder = new URIBuilder();
            StringBuilder queryStringBuilder = new StringBuilder();
            boolean hasQuery = !queryParams.isEmpty();
            for (Map.Entry<String, Collection<String>> stringCollectionEntry : queryParams.entrySet()) {
                String key = stringCollectionEntry.getKey();
                Collection<String> queryParamsValueList = stringCollectionEntry.getValue();
                if (request.isQueryParamsParseAsMultiValue()) {
                    for (String queryParamsValue : queryParamsValueList) {
                        uriBuilder.addParameter(key, queryParamsValue);
                        queryStringBuilder.append(key).append("=").append(queryParamsValue).append("&");
                    }
                } else {
                    String value = joiner.join(queryParamsValueList);
                    uriBuilder.addParameter(key, value);
                    queryStringBuilder.append(key).append("=").append(value).append("&");
                }
            }
            uriBuilder.setFragment(requestUri.getFragment());
            uriBuilder.setHost(requestUri.getHost());
            uriBuilder.setPath(requestUri.getPath());
            uriBuilder.setPort(requestUri.getPort());
            uriBuilder.setScheme(requestUri.getScheme());
            uriBuilder.setUserInfo(requestUri.getUserInfo());
            try {

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

                    if (hasQuery) {
                        String query = queryStringBuilder.substring(0, queryStringBuilder.length() - 1);
                        requestUri = new URI(requestUri.getScheme() + "://" + requestUri.getHost() + ":"
                                + requestUri.getPort() + urlPath + "?" + query);
                    } else {
                        requestUri = new URI(requestUri.getScheme() + "://" + requestUri.getHost() + ":"
                                + requestUri.getPort() + urlPath);
                    }
                } else {
                    requestUri = uriBuilder.build();
                }
            } catch (URISyntaxException e) {
                LOGGER.warn("could not update uri: {}", requestUri);
            }
        }
        return requestUri;
    }

    private InternalServerProxyMetadata loadServersMetadataConfiguration() {

        Configuration subset = ConfigurationFactory.getConfiguration().subset(getApiName());
        final Iterator<String> 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, "");
        startEurekaClient = subset.getBoolean("http.startEurekaClient", true);

        final List<String> keys = new ArrayList<String>();

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

        Collections.sort(keys);

        List<Pair<String, Integer>> hostAndPortPairs = new CopyOnWriteArrayList<Pair<String, Integer>>();

        for (String key : keys) {

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

                String host = subset.getString(key);

                // trim the host name
                if (org.apache.commons.lang.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);

        return metadata;

    }

    public void execute(HttpRequest request, ResponseCallback responseCallback,
            LoadBalancerStrategy loadBalancerStrategy, String apiName) {
        executeWithLoadBalancer(request, responseCallback);

    }

    public void close() {
    }

    private HttpClientRequest<ByteBuf> buildNetflixHttpRequest(HttpRequest request, Joiner joiner) {
        HttpClientRequest<ByteBuf> httpRequest = null;
        HttpMethod httpMethod = HttpMethod.valueOf(request.getHttpMethod().name());

        URI uri = buildUri(request, joiner);

        httpRequest = HttpClientRequest.create(httpMethod, uri.toString());
        byte[] entity = request.getEntity();
        if (entity != null) {
            httpRequest.withContent(entity);
        }

        Map<String, Collection<String>> headers = request.getHeaders();
        for (Map.Entry<String, Collection<String>> stringCollectionEntry : headers.entrySet()) {
            String key = stringCollectionEntry.getKey();
            Collection<String> stringCollection = stringCollectionEntry.getValue();
            String value = joiner.join(stringCollection);
            httpRequest.withHeader(key, value);
        }

        if (StringUtils.isNoneEmpty(request.getContentType())) {
            String contentType = request.getContentType();
            httpRequest.withHeader("Content-Type", contentType);
        }

        return httpRequest;
    }

    private static class NettyNetflixRetryHandler implements RetryHandler {
        InternalServerProxyMetadata metadata = null;

        public NettyNetflixRetryHandler(InternalServerProxyMetadata metadata) {
            this.metadata = metadata;
        }

        @Override
        public boolean isRetriableException(Throwable e, boolean sameServer) {
            return e instanceof IOException;
        }

        @Override
        public boolean isCircuitTrippingException(Throwable e) {
            return e instanceof IOException;
        }

        @Override
        public int getMaxRetriesOnSameServer() {
            return metadata.getNumberOfAttempts();
        }

        @Override
        public int getMaxRetriesOnNextServer() {
            return metadata.getNumberOfAttempts();
        }
    }

}