Repository: clickhouse/clickhouse-java
Issue: BasicAuthentication Support

Is basic authentication supported in the most recent version of the clickhouse JDBC drivers? (`clickhouse-jdbc-0.3.2-patch11` is my version).

I found an older PR saying that everyone got switched over https://github.com/ClickHouse/clickhouse-jdbc/issues/522, but looks like it may not be true in the most revent version.

I tried using the `custom_http_headers` property to set my own auth header, but the client complains about double auth because https://github.com/ClickHouse/clickhouse-jdbc/blob/f285b73d45597e29cf36dff4daf4cb4f9f53453a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnection.java#L166 this method is still always attempting to add the `X-Clickhouse` user and password methods. I can't find anywhere in the codebase that ever attempts to set an accessToken based method.

Am I missing a way to properly configure the client to use a `BasicAuth` header?

Changed files:
clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnection.java
clickhouse-http-client/src/main/java/com/clickhouse/client/http/HttpUrlConnectionImpl.java
clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java
clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java
clickhouse-http-client/src/main/java11/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java
clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
clickhouse-http-client/src/main/java11/com/clickhouse/client/http/HttpClientConnectionImpl.java
clickhouse-client/src/test/java/com/clickhouse/client/ClientIntegrationTest.java

diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java
index 578825d3..7a4e8fd4 100644
--- a/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java
+++ b/clickhouse-client/src/test/java/com/clickhouse/client/BaseIntegrationTest.java
@@ -19,6 +19,10 @@ public abstract class BaseIntegrationTest {
         return ClickHouseServerForTest.getClickHouseNode(protocol, true, ClickHouseNode.builder().build());
     }
 
+    protected ClickHouseNode getSecureServer(ClickHouseProtocol protocol, ClickHouseNode base) {
+        return ClickHouseServerForTest.getClickHouseNode(protocol, true, base);
+    }
+
     protected ClickHouseNode getServer(ClickHouseProtocol protocol) {
         return ClickHouseServerForTest.getClickHouseNode(protocol, false, ClickHouseNode.builder().build());
     }
diff --git a/clickhouse-client/src/test/java/com/clickhouse/client/ClientIntegrationTest.java b/clickhouse-client/src/test/java/com/clickhouse/client/ClientIntegrationTest.java
index bf9e3897..9c4e616f 100644
--- a/clickhouse-client/src/test/java/com/clickhouse/client/ClientIntegrationTest.java
+++ b/clickhouse-client/src/test/java/com/clickhouse/client/ClientIntegrationTest.java
@@ -101,12 +101,14 @@ public abstract class ClientIntegrationTest extends BaseIntegrationTest {
         return builder;
     }
 
-    protected ClickHouseClient getClient() {
-        return initClient(ClickHouseClient.builder()).nodeSelector(ClickHouseNodeSelector.of(getProtocol())).build();
+    protected ClickHouseClient getClient(ClickHouseConfig... configs) {
+        return initClient(ClickHouseClient.builder()).config(new ClickHouseConfig(configs))
+                .nodeSelector(ClickHouseNodeSelector.of(getProtocol())).build();
     }
 
-    protected ClickHouseClient getSecureClient() {
+    protected ClickHouseClient getSecureClient(ClickHouseConfig... configs) {
         return initClient(ClickHouseClient.builder())
+                .config(new ClickHouseConfig(configs))
                 .nodeSelector(ClickHouseNodeSelector.of(getProtocol()))
                 .option(ClickHouseClientOption.SSL, true)
                 .option(ClickHouseClientOption.SSL_MODE, ClickHouseSslMode.STRICT)
@@ -117,14 +119,22 @@ public abstract class ClientIntegrationTest extends BaseIntegrationTest {
                 .build();
     }
 
-    protected ClickHouseNode getServer() {
-        return getServer(getProtocol());
+    protected ClickHouseNode getSecureServer(ClickHouseNode base) {
+        return getSecureServer(getProtocol(), base);
     }
 
     protected ClickHouseNode getSecureServer() {
         return getSecureServer(getProtocol());
     }
 
+    protected ClickHouseNode getServer(ClickHouseNode base) {
+        return getServer(getProtocol(), base);
+    }
+
+    protected ClickHouseNode getServer() {
+        return getServer(getProtocol());
+    }
+
     @DataProvider(name = "compressionMatrix")
     protected Object[][] getCompressionMatrix() {
         ClickHouseFormat[] formats = new ClickHouseFormat[] {
@@ -1402,7 +1412,11 @@ public abstract class ClientIntegrationTest extends BaseIntegrationTest {
             request.query("drop temporary table if exists my_temp_table").execute().get();
             request.query("create temporary table my_temp_table(a Int8)").execute().get();
             request.query("insert into my_temp_table values(2)").execute().get();
-            request.write().table("my_temp_table").data(new ByteArrayInputStream(new byte[] { 3 })).execute().get();
+            try (ClickHouseResponse resp = request.write().table("my_temp_table")
+                    .data(new ByteArrayInputStream(new byte[] { 3 })).executeAndWait()) {
+                // ignore
+            }
+
             int count = 0;
             try (ClickHouseResponse resp = request.format(ClickHouseFormat.RowBinaryWithNamesAndTypes)
                     .query("select * from my_temp_table order by a").execute().get()) {
diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnection.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnection.java
index 9917f124..ce9aae26 100644
--- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnection.java
+++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnection.java
@@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Map.Entry;
@@ -158,39 +159,52 @@ public abstract class ClickHouseHttpConnection implements AutoCloseable {
 
     protected static Map<String, String> createDefaultHeaders(ClickHouseConfig config, ClickHouseNode server) {
         Map<String, String> map = new LinkedHashMap<>();
+        boolean hasAuthorizationHeader = false;
         // add customer headers
-        map.putAll(ClickHouseOption.toKeyValuePairs(config.getStrOption(ClickHouseHttpOption.CUSTOM_HEADERS)));
-        map.put("Accept", "*/*");
+        for (Entry<String, String> header : ClickHouseOption
+                .toKeyValuePairs(config.getStrOption(ClickHouseHttpOption.CUSTOM_HEADERS)).entrySet()) {
+            String name = header.getKey().toLowerCase(Locale.ROOT);
+            String value = header.getValue();
+            if (value == null) {
+                continue;
+            }
+            if ("authorization".equals(name)) {
+                hasAuthorizationHeader = true;
+            }
+            map.put(name, value);
+        }
+
+        map.put("accept", "*/*");
         if (!config.getBoolOption(ClickHouseHttpOption.KEEP_ALIVE)) {
-            map.put("Connection", "Close");
+            map.put("connection", "Close");
         }
-        map.put("User-Agent", config.getClientName());
+        map.put("user-agent", config.getClientName());
 
         ClickHouseCredentials credentials = server.getCredentials(config);
         if (credentials.useAccessToken()) {
             // TODO check if auth-scheme is available and supported
-            map.put("Authorization", credentials.getAccessToken());
-        } else {
-            map.put("X-ClickHouse-User", credentials.getUserName());
+            map.put("authorization", credentials.getAccessToken());
+        } else if (!hasAuthorizationHeader) {
+            map.put("x-clickhouse-user", credentials.getUserName());
             if (config.isSsl() && !ClickHouseChecker.isNullOrEmpty(config.getSslCert())) {
-                map.put("X-ClickHouse-SSL-Certificate-Auth", "on");
+                map.put("x-clickhouse-ssl-certificate-auth", "on");
             } else if (!ClickHouseChecker.isNullOrEmpty(credentials.getPassword())) {
-                map.put("X-ClickHouse-Key", credentials.getPassword());
+                map.put("x-clickhouse-key", credentials.getPassword());
             }
         }
 
         String database = server.getDatabase(config);
         if (!ClickHouseChecker.isNullOrEmpty(database)) {
-            map.put("X-ClickHouse-Database", database);
+            map.put("x-clickhouse-database", database);
         }
         // Also, you can use the ‘default_format’ URL parameter
-        map.put("X-ClickHouse-Format", config.getFormat().name());
+        map.put("x-clickhouse-format", config.getFormat().name());
         if (config.isResponseCompressed()) {
-            map.put("Accept-Encoding", config.getResponseCompressAlgorithm().encoding());
+            map.put("accept-encoding", config.getResponseCompressAlgorithm().encoding());
         }
         if (config.isRequestCompressed()
                 && config.getRequestCompressAlgorithm() != ClickHouseCompression.LZ4) {
-            map.put("Content-Encoding", config.getRequestCompressAlgorithm().encoding());
+            map.put("content-encoding", config.getRequestCompressAlgorithm().encoding());
         }
         return map;
     }
@@ -251,10 +265,12 @@ public abstract class ClickHouseHttpConnection implements AutoCloseable {
         Map<String, String> merged = new LinkedHashMap<>();
         merged.putAll(defaultHeaders);
         for (Entry<String, String> header : requestHeaders.entrySet()) {
-            if (header.getValue() == null) {
-                merged.remove(header.getKey());
+            String name = header.getKey().toLowerCase(Locale.ROOT);
+            String value = header.getValue();
+            if (value == null) {
+                merged.remove(name);
             } else {
-                merged.put(header.getKey(), header.getValue());
+                merged.put(name, value);
             }
         }
         return merged;
diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
index ac06e19a..a3c18ec5 100644
--- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
+++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
@@ -6,9 +6,12 @@ import java.util.concurrent.ExecutorService;
 import com.clickhouse.client.ClickHouseNode;
 import com.clickhouse.client.ClickHouseRequest;
 
-public abstract class ClickHouseHttpConnectionFactory {
+public final class ClickHouseHttpConnectionFactory {
     public static ClickHouseHttpConnection createConnection(ClickHouseNode server, ClickHouseRequest<?> request,
             ExecutorService executor) throws IOException {
         return new HttpUrlConnectionImpl(server, request, executor);
     }
+
+    private ClickHouseHttpConnectionFactory() {
+    }
 }
diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/HttpUrlConnectionImpl.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/HttpUrlConnectionImpl.java
index fd293590..ca9bb9fa 100644
--- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/HttpUrlConnectionImpl.java
+++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/HttpUrlConnectionImpl.java
@@ -45,11 +45,11 @@ import javax.net.ssl.SSLContext;
 public class HttpUrlConnectionImpl extends ClickHouseHttpConnection {
     private static final Logger log = LoggerFactory.getLogger(HttpUrlConnectionImpl.class);
 
-    private static final byte[] HEADER_CONTENT_DISPOSITION = "Content-Disposition: form-data; name=\\""
+    private static final byte[] HEADER_CONTENT_DISPOSITION = "content-disposition: form-data; name=\\""
             .getBytes(StandardCharsets.US_ASCII);
-    private static final byte[] HEADER_OCTET_STREAM = "Content-Type: application/octet-stream\\r\\n"
+    private static final byte[] HEADER_OCTET_STREAM = "content-type: application/octet-stream\\r\\n"
             .getBytes(StandardCharsets.US_ASCII);
-    private static final byte[] HEADER_BINARY_ENCODING = "Content-Transfer-Encoding: binary\\r\\n\\r\\n"
+    private static final byte[] HEADER_BINARY_ENCODING = "content-transfer-encoding: binary\\r\\n\\r\\n"
             .getBytes(StandardCharsets.US_ASCII);
 
     private static final byte[] DOUBLE_DASH = new byte[] { '-', '-' };
@@ -217,14 +217,14 @@ public class HttpUrlConnectionImpl extends ClickHouseHttpConnection {
     protected ClickHouseHttpResponse post(String sql, ClickHouseInputStream data, List<ClickHouseExternalTable> tables,
             String url, Map<String, String> headers, ClickHouseConfig config, Runnable postCloseAction)
             throws IOException {
-        Charset charset = StandardCharsets.US_ASCII;
+        Charset ascii = StandardCharsets.US_ASCII;
         byte[] boundary = null;
         if (tables != null && !tables.isEmpty()) {
             String uuid = UUID.randomUUID().toString();
-            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=".concat(uuid));
-            boundary = uuid.getBytes(charset);
+            conn.setRequestProperty("content-type", "multipart/form-data; boundary=".concat(uuid));
+            boundary = uuid.getBytes(ascii);
         } else {
-            conn.setRequestProperty("Content-Type", "text/plain; charset=UTF-8");
+            conn.setRequestProperty("content-type", "text/plain; charset=UTF-8");
         }
         setHeaders(conn, headers);
 
@@ -241,7 +241,8 @@ public class HttpUrlConnectionImpl extends ClickHouseHttpConnection {
                 : (hasInput
                         ? ClickHouseClient.getAsyncRequestOutputStream(config, conn.getOutputStream(), null) // latch::countDown)
                         : ClickHouseClient.getRequestOutputStream(c, conn.getOutputStream(), null))) {
-            byte[] sqlBytes = hasFile ? new byte[0] : sql.getBytes(StandardCharsets.UTF_8);
+            Charset utf8 = StandardCharsets.UTF_8;
+            byte[] sqlBytes = hasFile ? new byte[0] : sql.getBytes(utf8);
             if (boundary != null) {
                 out.writeBytes(LINE_PREFIX);
                 out.writeBytes(boundary);
@@ -250,7 +251,7 @@ public class HttpUrlConnectionImpl extends ClickHouseHttpConnection {
                 out.writeBytes(SUFFIX_QUERY);
                 out.writeBytes(sqlBytes);
                 for (ClickHouseExternalTable t : tables) {
-                    byte[] tableName = t.getName().getBytes(StandardCharsets.UTF_8);
+                    byte[] tableName = t.getName().getBytes(utf8);
                     for (int i = 0; i < 3; i++) {
                         out.writeBytes(LINE_PREFIX);
                         out.writeBytes(boundary);
@@ -259,10 +260,10 @@ public class HttpUrlConnectionImpl extends ClickHouseHttpConnection {
                         out.writeBytes(tableName);
                         if (i == 0) {
                             out.writeBytes(SUFFIX_FORMAT);
-                            out.writeBytes(t.getFormat().name().getBytes(charset));
+                            out.writeBytes(t.getFormat().name().getBytes(ascii));
                         } else if (i == 1) {
                             out.writeBytes(SUFFIX_STRUCTURE);
-                            out.writeBytes(t.getStructure().getBytes(StandardCharsets.UTF_8));
+                            out.writeBytes(t.getStructure().getBytes(utf8));
                         } else {
                             out.writeBytes(SUFFIX_FILENAME);
                             out.writeBytes(tableName);
diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java
index cbc7d931..1d7d81e8 100644
--- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java
+++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java
@@ -19,7 +19,9 @@ public enum ClickHouseHttpOption implements ClickHouseOption {
      */
     CUSTOM_HEADERS("custom_http_headers", "", "Custom HTTP headers."),
     /**
-     * Custom HTTP query parameters.
+     * Custom HTTP query parameters. Consider
+     * {@link com.clickhouse.client.config.ClickHouseClientOption#CUSTOM_SETTINGS}
+     * if you don't want your implementation ties to http protocol.
      */
     CUSTOM_PARAMS("custom_http_params", "", "Custom HTTP query parameters."),
     /**
diff --git a/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java b/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
index 024be20e..9bd6d54b 100644
--- a/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
+++ b/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/ClickHouseHttpConnectionFactory.java
@@ -8,7 +8,7 @@ import com.clickhouse.client.ClickHouseRequest;
 import com.clickhouse.client.http.config.ClickHouseHttpOption;
 import com.clickhouse.client.http.config.HttpConnectionProvider;
 
-public abstract class ClickHouseHttpConnectionFactory {
+public final class ClickHouseHttpConnectionFactory {
     public static ClickHouseHttpConnection createConnection(ClickHouseNode server, ClickHouseRequest<?> request,
             ExecutorService executor) throws IOException {
         HttpConnectionProvider provider = request.getConfig().getOption(ClickHouseHttpOption.CONNECTION_PROVIDER,
@@ -24,4 +24,7 @@ public abstract class ClickHouseHttpConnectionFactory {
             return new HttpUrlConnectionImpl(server, request, executor);
         }
     }
+
+    private ClickHouseHttpConnectionFactory() {
+    }
 }
diff --git a/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/HttpClientConnectionImpl.java b/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/HttpClientConnectionImpl.java
index f0a5d94f..9bafbe1d 100644
--- a/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/HttpClientConnectionImpl.java
+++ b/clickhouse-http-client/src/main/java11/com/clickhouse/client/http/HttpClientConnectionImpl.java
@@ -41,6 +41,7 @@ import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.net.http.HttpClient.Redirect;
 import java.net.http.HttpClient.Version;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.List;
@@ -77,6 +78,23 @@ public class HttpClientConnectionImpl extends ClickHouseHttpConnection {
 
     private static final Logger log = LoggerFactory.getLogger(HttpClientConnectionImpl.class);
 
+    private static final byte[] HEADER_CONTENT_DISPOSITION = "content-disposition: form-data; name=\\""
+            .getBytes(StandardCharsets.US_ASCII);
+    private static final byte[] HEADER_OCTET_STREAM = "content-type: application/octet-stream\\r\\n"
+            .getBytes(StandardCharsets.US_ASCII);
+    private static final byte[] HEADER_BINARY_ENCODING = "content-transfer-encoding: binary\\r\\n\\r\\n"
+            .getBytes(StandardCharsets.US_ASCII);
+
+    private static final byte[] DOUBLE_DASH = new byte[] { '-', '-' };
+    private static final byte[] END_OF_NAME = new byte[] { '"', '\\r', '\\n' };
+    private static final byte[] LINE_PREFIX = new byte[] { '\\r', '\\n', '-', '-' };
+    private static final byte[] LINE_SUFFIX = new byte[] { '\\r', '\\n' };
+
+    private static final byte[] SUFFIX_QUERY = "query\\"\\r\\n\\r\\n".getBytes(StandardCharsets.US_ASCII);
+    private static final byte[] SUFFIX_FORMAT = "_format\\"\\r\\n\\r\\n".getBytes(StandardCharsets.US_ASCII);
+    private static final byte[] SUFFIX_STRUCTURE = "_structure\\"\\r\\n\\r\\n".getBytes(StandardCharsets.US_ASCII);
+    private static final byte[] SUFFIX_FILENAME = "\\"; filename=\\"".getBytes(StandardCharsets.US_ASCII);
+
     private final HttpClient httpClient;
     private final HttpRequest pingRequest;
 
@@ -205,55 +223,68 @@ public class HttpClientConnectionImpl extends ClickHouseHttpConnection {
                 responseInfo -> new ClickHouseResponseHandler(config.getMaxQueuedBuffers(), config.getSocketTimeout()));
     }
 
-    private ClickHouseHttpResponse postStream(ClickHouseConfig config, HttpRequest.Builder reqBuilder, String boundary,
+    private ClickHouseHttpResponse postStream(ClickHouseConfig config, HttpRequest.Builder reqBuilder, byte[] boundary,
             String sql, ClickHouseInputStream data, List<ClickHouseExternalTable> tables, Runnable postAction)
             throws IOException {
+        Charset ascii = StandardCharsets.US_ASCII;
+        ClickHouseConfig c = config;
         final boolean hasFile = data != null && data.getUnderlyingFile().isAvailable();
-        ClickHousePipedOutputStream stream = ClickHouseDataStreamFactory.getInstance().createPipedOutputStream(config,
+
+        ClickHousePipedOutputStream stream = ClickHouseDataStreamFactory.getInstance().createPipedOutputStream(c,
                 null);
         reqBuilder.POST(HttpRequest.BodyPublishers.ofInputStream(stream::getInputStream));
-
         // running in async is necessary to avoid deadlock of the piped stream
         CompletableFuture<HttpResponse<InputStream>> f = postRequest(reqBuilder.build());
-        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8))) {
-            if (boundary != null) {
-                String line = "\\r\\n--" + boundary + "\\r\\n";
-                writer.write(line);
-                writer.write("Content-Disposition: form-data; name=\\"query\\"\\r\\n\\r\\n");
-                writer.write(sql);
 
+        try (ClickHouseOutputStream out = stream) {
+            Charset utf8 = StandardCharsets.UTF_8;
+            byte[] sqlBytes = hasFile ? new byte[0] : sql.getBytes(utf8);
+
+            if (boundary != null) {
+                out.writeBytes(LINE_PREFIX);
+                out.writeBytes(boundary);
+                out.writeBytes(LINE_SUFFIX);
+                out.writeBytes(HEADER_CONTENT_DISPOSITION);
+                out.writeBytes(SUFFIX_QUERY);
+                out.writeBytes(sqlBytes);
                 for (ClickHouseExternalTable t : tables) {
-                    String tableName = t.getName();
-                    StringBuilder builder = new StringBuilder();
-                    builder.append(line).append("Content-Disposition: form-data; name=\\"").append(tableName)
-                            .append("_format\\"\\r\\n\\r\\n").append(t.getFormat().name());
-                    builder.append(line).append("Content-Disposition: form-data; name=\\"").append(tableName)
-                            .append("_structure\\"\\r\\n\\r\\n").append(t.getStructure());
-                    builder.append(line).append("Content-Disposition: form-data; name=\\"").append(tableName)
-                            .append("\\"; filename=\\"").append(tableName).append("\\"\\r\\n")
-                            .append("Content-Type: application/octet-stream\\r\\n")
-                            .append("Content-Transfer-Encoding: binary\\r\\n\\r\\n");
-                    writer.write(builder.toString());
-                    writer.flush();
-
-                    ClickHouseInputStream.pipe(t.getContent(), stream, config.getWriteBufferSize());
+                    byte[] tableName = t.getName().getBytes(utf8);
+                    for (int i = 0; i < 3; i++) {
+                        out.writeBytes(LINE_PREFIX);
+                        out.writeBytes(boundary);
+                        out.writeBytes(LINE_SUFFIX);
+                        out.writeBytes(HEADER_CONTENT_DISPOSITION);
+                        out.writeBytes(tableName);
+                        if (i == 0) {
+                            out.writeBytes(SUFFIX_FORMAT);
+                            out.writeBytes(t.getFormat().name().getBytes(ascii));
+                        } else if (i == 1) {
+                            out.writeBytes(SUFFIX_STRUCTURE);
+                            out.writeBytes(t.getStructure().getBytes(utf8));
+                        } else {
+                            out.writeBytes(SUFFIX_FILENAME);
+                            out.writeBytes(tableName);
+                            out.writeBytes(END_OF_NAME);
+                            break;
+                        }
+                    }
+                    out.writeBytes(HEADER_OCTET_STREAM);
+                    out.writeBytes(HEADER_BINARY_ENCODING);
+                    ClickHouseInputStream.pipe(t.getContent(), out, c.getWriteBufferSize());
                 }
 
-                writer.write("\\r\\n--" + boundary + "--\\r\\n");
-                writer.flush();
+                out.writeBytes(LINE_PREFIX);
+                out.writeBytes(boundary);
+                out.writeBytes(DOUBLE_DASH);
+                out.writeBytes(LINE_SUFFIX);
             } else {
-                if (!hasFile) {
-                    writer.write(sql);
-                    writer.flush();
-                }
-
-                if (data.available() > 0) {
+                out.writeBytes(sqlBytes);
+                if (data != null && data.available() > 0) {
                     // append \\n
-                    if (!hasFile && sql.charAt(sql.length() - 1) != '\\n') {
-                        stream.write(10);
+                    if (sqlBytes.length > 0 && sqlBytes[sqlBytes.length - 1] != (byte) '\\n') {
+                        out.write(10);
                     }
-
-                    ClickHouseInputStream.pipe(data, stream, config.getWriteBufferSize());
+                    ClickHouseInputStream.pipe(data, out, c.getWriteBufferSize());
                 }
             }
         }
@@ -303,12 +334,13 @@ public class HttpClientConnectionImpl extends ClickHouseHttpConnection {
         HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
                 .uri(URI.create(ClickHouseChecker.isNullOrEmpty(url) ? this.url : url))
                 .timeout(Duration.ofMillis(c.getSocketTimeout()));
-        String boundary = null;
+        byte[] boundary = null;
         if (tables != null && !tables.isEmpty()) {
-            boundary = UUID.randomUUID().toString();
-            reqBuilder.setHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
+            String uuid = UUID.randomUUID().toString();
+            reqBuilder.setHeader("content-type", "multipart/form-data; boundary=" + uuid);
+            boundary = uuid.getBytes(StandardCharsets.US_ASCII);
         } else {
-            reqBuilder.setHeader("Content-Type", "text/plain; charset=UTF-8");
+            reqBuilder.setHeader("content-type", "text/plain; charset=UTF-8");
         }
 
         headers = mergeHeaders(headers);
diff --git a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java
index c915196d..c50cd83f 100644
--- a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java
+++ b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ClickHouseHttpClientTest.java
@@ -3,6 +3,7 @@ package com.clickhouse.client.http;
 import java.util.UUID;
 
 import com.clickhouse.client.ClickHouseClient;
+import com.clickhouse.client.ClickHouseConfig;
 import com.clickhouse.client.ClickHouseCredentials;
 import com.clickhouse.client.ClickHouseFormat;
 import com.clickhouse.client.ClickHouseNode;
@@ -35,6 +36,36 @@ public class ClickHouseHttpClientTest extends ClientIntegrationTest {
         return ClickHouseHttpClient.class;
     }
 
+    @Test(groups = "integration")
+    public void testAuthentication() throws Exception {
+        String sql = "select currentUser()";
+        try (ClickHouseClient client = getClient(
+                new ClickHouseConfig(null, ClickHouseCredentials.fromUserAndPassword("dba", "dba"), null, null));
+                ClickHouseResponse response = client
+                        .connect(getServer())
+                        // .option(ClickHouseHttpOption.CUSTOM_PARAMS, "user=dba,password=incorrect")
+                        .query(sql).executeAndWait()) {
+            Assert.assertEquals(response.firstRecord().getValue(0).asString(), "dba");
+        }
+
+        try (ClickHouseClient client = getClient();
+                ClickHouseResponse response = client
+                        .connect(getServer())
+                        .option(ClickHouseHttpOption.CUSTOM_HEADERS, "Authorization=Basic ZGJhOmRiYQ==")
+                        // .option(ClickHouseHttpOption.CUSTOM_PARAMS, "user=dba,password=incorrect")
+                        .query(sql).executeAndWait()) {
+            Assert.assertEquals(response.firstRecord().getValue(0).asString(), "dba");
+        }
+
+        try (ClickHouseClient client = getClient();
+                ClickHouseResponse response = client
+                        .connect(getServer(ClickHouseNode
+                                .of("http://localhost?custom_http_headers=aUthorization%3DBasic%20ZGJhOmRiYQ%3D%3D")))
+                        .query(sql).executeAndWait()) {
+            Assert.assertEquals(response.firstRecord().getValue(0).asString(), "dba");
+        }
+    }
+
     @Test(groups = "integration")
     @Override
     public void testSession() throws Exception {