org.apache.calcite.avatica.remote.AvaticaCommonsHttpClientSpnegoImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.calcite.avatica.remote.AvaticaCommonsHttpClientSpnegoImpl.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you 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 org.apache.calcite.avatica.remote;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.KerberosCredentials;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import org.ietf.jgss.GSSCredential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Principal;
import java.util.Objects;

/**
 * Implementation of an AvaticaHttpClient which uses SPNEGO.
 */
public class AvaticaCommonsHttpClientSpnegoImpl implements AvaticaHttpClient {
    private static final Logger LOG = LoggerFactory.getLogger(AvaticaCommonsHttpClientSpnegoImpl.class);

    public static final String CACHED_CONNECTIONS_MAX_KEY = "avatica.http.spnego.max_cached";
    public static final String CACHED_CONNECTIONS_MAX_DEFAULT = "100";
    public static final String CACHED_CONNECTIONS_MAX_PER_ROUTE_KEY = "avatica.http.spnego.max_per_route";
    public static final String CACHED_CONNECTIONS_MAX_PER_ROUTE_DEFAULT = "25";

    private static final boolean USE_CANONICAL_HOSTNAME = true;
    private static final boolean STRIP_PORT_ON_SERVER_LOOKUP = true;

    final URL url;
    final HttpHost host;
    final PoolingHttpClientConnectionManager pool;
    final Lookup<AuthSchemeProvider> authRegistry;
    final BasicCredentialsProvider credentialsProvider;
    final BasicAuthCache authCache;
    final CloseableHttpClient client;

    /**
     * Constructs an http client with the expectation that the user is already logged in with their
     * Kerberos identity via JAAS.
     *
     * @param url The URL for the Avatica server
     */
    public AvaticaCommonsHttpClientSpnegoImpl(URL url) {
        this(url, null);
    }

    /**
     * Constructs an HTTP client with user specified by the given credentials.
     *
     * @param url The URL for the Avatica server
     * @param credential The GSS credentials
     */
    public AvaticaCommonsHttpClientSpnegoImpl(URL url, GSSCredential credential) {
        this.url = Objects.requireNonNull(url);

        pool = new PoolingHttpClientConnectionManager();
        // Increase max total connection to 100
        final String maxCnxns = System.getProperty(CACHED_CONNECTIONS_MAX_KEY, CACHED_CONNECTIONS_MAX_DEFAULT);
        pool.setMaxTotal(Integer.parseInt(maxCnxns));
        // Increase default max connection per route to 25
        final String maxCnxnsPerRoute = System.getProperty(CACHED_CONNECTIONS_MAX_PER_ROUTE_KEY,
                CACHED_CONNECTIONS_MAX_PER_ROUTE_DEFAULT);
        pool.setDefaultMaxPerRoute(Integer.parseInt(maxCnxnsPerRoute));

        this.host = new HttpHost(url.getHost(), url.getPort());

        this.authRegistry = RegistryBuilder.<AuthSchemeProvider>create().register(AuthSchemes.SPNEGO,
                new SPNegoSchemeFactory(STRIP_PORT_ON_SERVER_LOOKUP, USE_CANONICAL_HOSTNAME)).build();

        this.credentialsProvider = new BasicCredentialsProvider();
        if (null != credential) {
            // Non-null credential should be used directly with KerberosCredentials.
            this.credentialsProvider.setCredentials(AuthScope.ANY, new KerberosCredentials(credential));
        } else {
            // A null credential implies that the user is logged in via JAAS using the
            // java.security.auth.login.config system property
            this.credentialsProvider.setCredentials(AuthScope.ANY, EmptyCredentials.INSTANCE);
        }

        this.authCache = new BasicAuthCache();

        // A single thread-safe HttpClient, pooling connections via the ConnectionManager
        this.client = HttpClients.custom().setDefaultAuthSchemeRegistry(authRegistry).setConnectionManager(pool)
                .build();
    }

    @Override
    public byte[] send(byte[] request) {
        HttpClientContext context = HttpClientContext.create();

        context.setTargetHost(host);
        context.setCredentialsProvider(credentialsProvider);
        context.setAuthSchemeRegistry(authRegistry);
        context.setAuthCache(authCache);

        ByteArrayEntity entity = new ByteArrayEntity(request, ContentType.APPLICATION_OCTET_STREAM);

        // Create the client with the AuthSchemeRegistry and manager
        HttpPost post = new HttpPost(toURI(url));
        post.setEntity(entity);

        try (CloseableHttpResponse response = client.execute(post, context)) {
            final int statusCode = response.getStatusLine().getStatusCode();
            if (HttpURLConnection.HTTP_OK == statusCode || HttpURLConnection.HTTP_INTERNAL_ERROR == statusCode) {
                return EntityUtils.toByteArray(response.getEntity());
            }

            throw new RuntimeException("Failed to execute HTTP Request, got HTTP/" + statusCode);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            LOG.debug("Failed to execute HTTP request", e);
            throw new RuntimeException(e);
        }
    }

    private static URI toURI(URL url) throws RuntimeException {
        try {
            return url.toURI();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * A credentials implementation which returns null.
     */
    private static class EmptyCredentials implements Credentials {
        public static final EmptyCredentials INSTANCE = new EmptyCredentials();

        @Override
        public String getPassword() {
            return null;
        }

        @Override
        public Principal getUserPrincipal() {
            return null;
        }
    }
}

// End AvaticaCommonsHttpClientSpnegoImpl.java