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

com.clickhouse.client.http.ClickHouseHttpClient Maven / Gradle / Ivy

There is a newer version: 0.7.1-patch1
Show newest version
package com.clickhouse.client.http;

import com.clickhouse.client.AbstractClient;
import com.clickhouse.client.ClickHouseConfig;
import com.clickhouse.client.ClickHouseException;
import com.clickhouse.client.ClickHouseNode;
import com.clickhouse.client.ClickHouseProtocol;
import com.clickhouse.client.ClickHouseRequest;
import com.clickhouse.client.ClickHouseResponse;
import com.clickhouse.client.ClickHouseStreamResponse;
import com.clickhouse.client.ClickHouseTransaction;
import com.clickhouse.client.http.config.ClickHouseHttpOption;
import com.clickhouse.config.ClickHouseOption;
import com.clickhouse.data.ClickHouseChecker;
import com.clickhouse.logging.Logger;
import com.clickhouse.logging.LoggerFactory;

import java.io.IOException;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.stream.Collectors;

public class ClickHouseHttpClient extends AbstractClient {
    private static final Logger log = LoggerFactory.getLogger(ClickHouseHttpClient.class);

    static final List SUPPORTED = Collections.singletonList(ClickHouseProtocol.HTTP);

    public static class HostNameAndAddress{
        public String hostName;
        public String address;
    }

    private static HostNameAndAddress getLocalHost() {
        // get local address but not localhost
        HostNameAndAddress hostNameAndAddress = new HostNameAndAddress();
        try {
            Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces();

            outer:
            for (NetworkInterface ni : Collections.list(networkInterfaces)) {
                Enumeration inetAddresses = ni.getInetAddresses();
                for (InetAddress ia : Collections.list(inetAddresses)) {
                    // We just use the first non-loopback address
                    if (!ia.isLoopbackAddress() && !ia.isLinkLocalAddress()) {
                        hostNameAndAddress.address = ia.getHostAddress();
                        hostNameAndAddress.hostName = ia.getCanonicalHostName();
                        break outer;
                    }
                }
            }
        } catch (SocketException e) {
            // ignore
        }
        return hostNameAndAddress;
    }

    public static String getReferer(ClickHouseConfig config) {
        String referer = null;
        if (!ClickHouseChecker.isNullOrEmpty(config.getStrOption(ClickHouseHttpOption.SEND_HTTP_CLIENT_ID)))
        {
            if (ClickHouseChecker.isNullOrEmpty(ClickHouseOption.toKeyValuePairs(config.getStrOption(ClickHouseHttpOption.CUSTOM_HEADERS)).get("referer"))) {
                if (config.getStrOption(ClickHouseHttpOption.SEND_HTTP_CLIENT_ID).equals("HOST_NAME"))
                    referer = LOCAL_HOST.hostName;
                else
                    referer = LOCAL_HOST.address;
            }
        }
        return referer;
    }

    public static HostNameAndAddress LOCAL_HOST = null;

    public ClickHouseHttpClient() {
        synchronized (this) {
            if (LOCAL_HOST == null)
                LOCAL_HOST = getLocalHost();
        }
    }

    protected ConcurrentSkipListSet roles = new ConcurrentSkipListSet<>();

    @Override
    protected boolean checkConnection(ClickHouseHttpConnection connection, ClickHouseNode requestServer,
            ClickHouseNode currentServer, ClickHouseRequest request) {
        // return false to suggest creating a new connection
        return connection != null && connection.isReusable() && requestServer.isSameEndpoint(currentServer);
    }

    @Override
    protected boolean checkHealth(ClickHouseNode server, int timeout) {
        return getConnection(read(server)).ping(timeout);
    }

    @Override
    protected Collection getSupportedProtocols() {
        return SUPPORTED;
    }

    @Override
    protected ClickHouseHttpConnection newConnection(ClickHouseHttpConnection connection, ClickHouseNode server,
            ClickHouseRequest request) {
        if (connection != null && connection.isReusable()) {
            closeConnection(connection, false);
        }

        try {

            return ClickHouseHttpConnectionFactory.createConnection(server, request, getExecutor(), buildAdditionalReqParams(request));
        } catch (IOException e) {
            throw new CompletionException(e);
        }
    }

    @Override
    protected void closeConnection(ClickHouseHttpConnection connection, boolean force) {
        try {
            connection.close();
        } catch (Exception e) {
            log.warn("Failed to close http connection due to: %s", e.getMessage());
        }
    }

    protected String buildQueryParams(Map params) {
        if (params == null || params.isEmpty()) {
            return "";
        }

        StringBuilder builder = new StringBuilder();
        for (Map.Entry entry : params.entrySet()) {
            builder.append(ClickHouseHttpConnection.urlEncode(entry.getKey(), StandardCharsets.UTF_8)).append('=')
                    .append(ClickHouseHttpConnection.urlEncode(entry.getValue(), StandardCharsets.UTF_8)).append('&');
        }

        if (builder.length() > 0) {
            builder.setLength(builder.length() - 1);
        }
        return builder.toString();
    }

    private Map buildAdditionalReqParams(ClickHouseRequest sealedRequest) {
        ClickHouseConfig config = sealedRequest.getConfig();
        if (config.getBoolOption(ClickHouseHttpOption.REMEMBER_LAST_SET_ROLES)) {
            if (!(sealedRequest.hasSetting("_set_roles_stmt") || roles.isEmpty())) {
                return Collections.singletonMap("_roles", roles);
            }
        }
        return Collections.emptyMap();
    }

    @Override
    protected ClickHouseResponse send(ClickHouseRequest sealedRequest) throws ClickHouseException, IOException {
        ClickHouseHttpConnection conn = getConnection(sealedRequest);

        List stmts = sealedRequest.getStatements(false);
        int size = stmts.size();
        String sql;
        if (size == 0) {
            throw new IllegalArgumentException("At least one SQL statement is required for execution");
        } else if (size > 1) {
            throw new IllegalArgumentException("Expect one SQL statement to execute but we got " + size);
        } else {
            sql = stmts.get(0);
        }

        log.debug("Query: %s", sql);
        final ClickHouseConfig config = sealedRequest.getConfig();
        final ClickHouseHttpResponse httpResponse;
        final ClickHouseTransaction tx = sealedRequest.getTransaction();
        final Runnable postAction = tx != null && tx.isImplicit()
                ? () -> {
                    try {
                        tx.commit();
                    } catch (ClickHouseException e) {
                        throw new UncheckedIOException(new IOException(e.getMessage()));
                    }
                }
                : null;

        if (conn.isReusable()) {
            Map additionalParams = buildAdditionalReqParams(sealedRequest);

            ClickHouseNode server = sealedRequest.getServer();
            httpResponse = conn.post(config, sql, sealedRequest.getInputStream().orElse(null),
                    sealedRequest.getExternalTables(), sealedRequest.getOutputStream().orElse(null),
                    ClickHouseHttpConnection.buildUrl(server.getBaseUri(), sealedRequest, additionalParams),
                    ClickHouseHttpConnection.createDefaultHeaders(config, server, conn.getUserAgent(), getReferer(config)),
                    postAction);
        } else {
            httpResponse = conn.post(config, sql, sealedRequest.getInputStream().orElse(null),
                    sealedRequest.getExternalTables(), sealedRequest.getOutputStream().orElse(null), null, null,
                    postAction);
        }

        if (config.getBoolOption(ClickHouseHttpOption.REMEMBER_LAST_SET_ROLES)) {
            // At this point only successful responses are expected
            if (sealedRequest.hasSetting("_set_roles_stmt")) {
                rememberRoles((Set) sealedRequest.getSettings().get("_set_roles_stmt"));
            }
        }


        return ClickHouseStreamResponse.of(httpResponse.getConfig(sealedRequest), httpResponse.getInputStream(),
                sealedRequest.getSettings(), null, httpResponse.summary, httpResponse.getTimeZone());
    }

    @Override
    public final Class getOptionClass() {
        return ClickHouseHttpOption.class;
    }

    public void rememberRoles(Set requestedRoles) {
        roles.clear();
        if (requestedRoles != null) {
            roles.addAll(requestedRoles.stream().filter(r -> !"NONE".equalsIgnoreCase(r)).collect(Collectors.toList()));
        }
    }

    public Set getRoles() {
        return roles;
    }

    public void clearRoles() {
        roles.clear();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy