Implements azure core http client (#388)
* Introduces KustoQuery wrapper object * working on async client * updating client with KustoQuery object * updating client with KustoQuery object * updating client with KustoQuery object * will simplify later. * will simplify later. * will simplify later. * ran formatter, fixed test, fixed bad merge * AtomicBooleans in already modified test. Save some lines, might as well. * hide KustoQuery object * rename and hide KustoRequest * adding E2ETests for async queries * ran formatter * no complete in sink * move blocking operations off the io thread * run formatter * replace execute utilization * fixes teardown. async still doesn't work yet. * fixes msal4j issue * build.yaml * Introduces KustoQuery wrapper object * working on async client * updating client with KustoQuery object * updating client with KustoQuery object * updating client with KustoQuery object * will simplify later. * will simplify later. * will simplify later. * ran formatter, fixed test, fixed bad merge * AtomicBooleans in already modified test. Save some lines, might as well. * hide KustoQuery object * rename and hide KustoRequest * adding E2ETests for async queries * ran formatter * no complete in sink * move blocking operations off the io thread * run formatter * replace execute utilization * fixes teardown. async still doesn't work yet. * fixes msal4j issue * rebased * rework azure branch * remove unused dependencies * fix some more issues with imports * add default timeout 10 min * re-add readme * modified changelog * modified changelog * removes krL * remove async test file * remove closeable from query clients * fixes pom version change and removes unused reactor property * CI_EXECUTION * storageaccountset guard clause * close clients in E2ETest * remove extra assertions from teardown * remove reactor step verifiers for now * readd deflate to cloudinfo * renoved reactor test library for now * run formatter * added fixme * move URIBuilder to further facilitate removal of Apache * add more detail to assertion failure * Made InvalidConnectionString extend DataClientException so users can optionally catch the more specific exception * commenting everything async until implementation works * commenting everything async until implementation works * ran formatter * remove unused Apache classes * removed commented code * move message to appropriate assertion * removed last of the commented code and ran formatter * readd the auto-delete policy * make request object usage private * put back more Apache things. * renove trailing space * Update BaseClient.java --------- Co-authored-by: cole <cole.snyder@gmork.tech> Co-authored-by: asafmahlev <asafmahlev@microsoft.com> Co-authored-by: ohad bitton <32278684+ohadbitt@users.noreply.github.com>
This commit is contained in:
Родитель
fb832817eb
Коммит
d73eeb3663
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -4,11 +4,17 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [6.0.0] - 2024-09-21
|
||||
### Changed
|
||||
- Replaced Apache CloseableHttpClient with configurable azure-core client.
|
||||
- [BREAKING] HttpClientFactory now accepts clients implementing azure-core HttpClient.
|
||||
- [BREAKING] HttpClientProperties and HttpClientPropertiesBuilder now use azure-core ProxyOptions.
|
||||
- Data client now wraps internal HTTP client.
|
||||
- Moved HTTP request tracing logic into a builder class.
|
||||
- Moved HTTP request building logic into a builder class.
|
||||
- [BREAKING] Redirects are disabled by default. Use ClientRequestProperties "client_max_redirect_count" option
|
||||
to enable. Default changed to 0.
|
||||
|
||||
## [5.2.0] - 2024-08-27
|
||||
### Fixed
|
||||
- Used Msal user prompt old code which is deprecated in the new version coming from last bom update resulted in method not found exception.
|
||||
|
|
|
@ -59,7 +59,7 @@ And the SDK will know to use these values automatically.
|
|||
Alternatively, you can define a proxy programmatically when creating a client, using `HttpClientProperties`:
|
||||
```java
|
||||
HttpClientProperties httpClientProperties = HttpClientProperties.builder()
|
||||
.proxy(new HttpHost("1.2.3.4", 8989))
|
||||
.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("myproxy.contoso.com", 8080)))
|
||||
.build();
|
||||
|
||||
Client = ClientFactory.createClient(<engine_connection_string>, httpClientProperties);
|
||||
|
|
12
data/pom.xml
12
data/pom.xml
|
@ -167,7 +167,7 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
<version>${apache.httpclient.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
|
@ -211,11 +211,6 @@
|
|||
<version>${slf4j.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>${reactor-core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
|
@ -274,10 +269,5 @@
|
|||
<artifactId>resilience4j-core</artifactId>
|
||||
<version>${resilience4j.version}</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>io.vavr</groupId>-->
|
||||
<!-- <artifactId>vavr</artifactId>-->
|
||||
<!-- <version>${io.vavr.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
package com.microsoft.azure.kusto.data;
|
||||
|
||||
import com.azure.core.http.*;
|
||||
import com.azure.core.util.Context;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.microsoft.azure.kusto.data.exceptions.*;
|
||||
import com.microsoft.azure.kusto.data.http.CloseParentResourcesStream;
|
||||
import com.microsoft.azure.kusto.data.http.HttpRequestBuilder;
|
||||
import com.microsoft.azure.kusto.data.http.HttpStatus;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.conn.EofSensorInputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class BaseClient implements Client, StreamingClient {
|
||||
|
||||
private static final int MAX_REDIRECT_COUNT = 1;
|
||||
|
||||
// Make logger available to implementations
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public BaseClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
protected String post(HttpRequest request) throws DataServiceException {
|
||||
// Execute and get the response
|
||||
try (HttpResponse response = httpClient.sendSync(request, Context.NONE)) {
|
||||
return processResponseBody(response);
|
||||
}
|
||||
}
|
||||
|
||||
private String processResponseBody(HttpResponse response) throws DataServiceException {
|
||||
String responseBody = Utils.isGzipResponse(response) ? Utils.gzipedInputToString(response.getBodyAsBinaryData().toStream())
|
||||
: response.getBodyAsBinaryData().toString();
|
||||
|
||||
if (responseBody == null) {
|
||||
return null;
|
||||
}
|
||||
switch (response.getStatusCode()) {
|
||||
case HttpStatus.OK:
|
||||
return responseBody;
|
||||
case HttpStatus.TOO_MANY_REQS:
|
||||
throw new ThrottleException(response.getRequest().getUrl().toString());
|
||||
default:
|
||||
throw createExceptionFromResponse(response.getRequest().getUrl().toString(), response, null, responseBody);
|
||||
}
|
||||
}
|
||||
|
||||
protected InputStream postToStreamingOutput(HttpRequest request) throws DataServiceException {
|
||||
return postToStreamingOutput(request, 0);
|
||||
}
|
||||
|
||||
// Todo: Implement async version of this method
|
||||
protected InputStream postToStreamingOutput(HttpRequest request, int redirectCount) throws DataServiceException {
|
||||
|
||||
boolean returnInputStream = false;
|
||||
String errorFromResponse = null;
|
||||
|
||||
HttpResponse httpResponse = null;
|
||||
try {
|
||||
|
||||
httpResponse = httpClient.sendSync(request, Context.NONE);
|
||||
|
||||
int responseStatusCode = httpResponse.getStatusCode();
|
||||
|
||||
if (responseStatusCode == HttpStatus.OK) {
|
||||
returnInputStream = true;
|
||||
return new EofSensorInputStream(new CloseParentResourcesStream(httpResponse), null);
|
||||
}
|
||||
|
||||
errorFromResponse = httpResponse.getBodyAsBinaryData().toString();
|
||||
// Ideal to close here (as opposed to finally) so that if any data can't be flushed, the exception will be properly thrown and handled
|
||||
httpResponse.close();
|
||||
|
||||
if (shouldPostToOriginalUrlDueToRedirect(redirectCount, responseStatusCode)) {
|
||||
Optional<HttpHeader> redirectLocation = Optional.ofNullable(httpResponse.getHeaders().get(HttpHeaderName.LOCATION));
|
||||
if (redirectLocation.isPresent() && !redirectLocation.get().getValue().equals(request.getUrl().toString())) {
|
||||
HttpRequest redirectRequest = HttpRequestBuilder
|
||||
.fromExistingRequest(request)
|
||||
.withURL(redirectLocation.get().getValue())
|
||||
.build();
|
||||
return postToStreamingOutput(redirectRequest, redirectCount + 1);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new DataServiceException(request.getUrl().toString(),
|
||||
"postToStreamingOutput failed to get or decompress response stream", ex, false);
|
||||
} catch (Exception ex) {
|
||||
throw createExceptionFromResponse(request.getUrl().toString(), httpResponse, ex, errorFromResponse);
|
||||
} finally {
|
||||
closeResourcesIfNeeded(returnInputStream, httpResponse);
|
||||
}
|
||||
|
||||
throw createExceptionFromResponse(request.getUrl().toString(), httpResponse, null, errorFromResponse);
|
||||
}
|
||||
|
||||
public static DataServiceException createExceptionFromResponse(String url, HttpResponse httpResponse, Exception thrownException, String errorFromResponse) {
|
||||
if (httpResponse == null) {
|
||||
return new DataServiceException(url, "POST failed to send request", thrownException, false);
|
||||
}
|
||||
/*
|
||||
* TODO: When we add another streaming API that returns a KustoOperationResult, we'll need to handle the 2 types of content errors this API can return:
|
||||
* (1) Inline error (engine identifies error after it starts building the json result), or (2) in the KustoOperationResult's QueryCompletionInformation,
|
||||
* both of which present with "200 OK". See .Net's DataReaderParser.
|
||||
*/
|
||||
String activityId = determineActivityId(httpResponse);
|
||||
String message = errorFromResponse;
|
||||
WebException formattedException = new WebException(errorFromResponse, httpResponse, thrownException);
|
||||
boolean isPermanent = false;
|
||||
if (!StringUtils.isBlank(errorFromResponse)) {
|
||||
try {
|
||||
JsonNode jsonObject = Utils.getObjectMapper().readTree(errorFromResponse);
|
||||
if (jsonObject.has("error")) {
|
||||
formattedException = new DataWebException(errorFromResponse, httpResponse, thrownException);
|
||||
OneApiError apiError = ((DataWebException) formattedException).getApiError();
|
||||
message = apiError.getDescription();
|
||||
isPermanent = apiError.isPermanent();
|
||||
} else if (jsonObject.has("message")) {
|
||||
message = jsonObject.get("message").asText();
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
// It's not ideal to use an exception here for control flow, but we can't know if it's a valid JSON until we try to parse it
|
||||
LOGGER.debug("json processing error happened while parsing errorFromResponse {}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
message = String.format("Http StatusCode='%s'", httpResponse.getStatusCode());
|
||||
}
|
||||
|
||||
return new DataServiceException(
|
||||
url,
|
||||
String.format("%s, ActivityId='%s'", message, activityId),
|
||||
formattedException,
|
||||
isPermanent);
|
||||
}
|
||||
|
||||
private static void closeResourcesIfNeeded(boolean returnInputStream, HttpResponse httpResponse) {
|
||||
// If we close the resources after returning the InputStream to the user, he won't be able to read from it - used in streaming query
|
||||
if (!returnInputStream) {
|
||||
if (httpResponse != null) {
|
||||
httpResponse.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldPostToOriginalUrlDueToRedirect(int redirectCount, int status) {
|
||||
return (status == HttpStatus.FOUND || status == HttpStatus.TEMP_REDIRECT) && redirectCount + 1 <= MAX_REDIRECT_COUNT;
|
||||
}
|
||||
|
||||
private static String determineActivityId(HttpResponse httpResponse) {
|
||||
String activityId = "";
|
||||
|
||||
Optional<HttpHeader> activityIdHeader = Optional.ofNullable(
|
||||
httpResponse.getHeaders().get(HttpHeaderName.fromString("x-ms-activity-id")));
|
||||
if (activityIdHeader.isPresent()) {
|
||||
activityId = activityIdHeader.get().getValue();
|
||||
}
|
||||
return activityId;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,14 +6,7 @@ package com.microsoft.azure.kusto.data;
|
|||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface Client extends Closeable {
|
||||
KustoOperationResult execute(String command) throws DataServiceException, DataClientException;
|
||||
|
||||
KustoOperationResult execute(String database, String command) throws DataServiceException, DataClientException;
|
||||
|
||||
KustoOperationResult execute(String database, String command, ClientRequestProperties properties) throws DataServiceException, DataClientException;
|
||||
public interface Client {
|
||||
|
||||
KustoOperationResult executeQuery(String command) throws DataServiceException, DataClientException;
|
||||
|
||||
|
@ -27,7 +20,7 @@ public interface Client extends Closeable {
|
|||
|
||||
KustoOperationResult executeMgmt(String database, String command, ClientRequestProperties properties) throws DataServiceException, DataClientException;
|
||||
|
||||
String executeToJsonResult(String database) throws DataServiceException, DataClientException;
|
||||
String executeToJsonResult(String command) throws DataServiceException, DataClientException;
|
||||
|
||||
String executeToJsonResult(String database, String command) throws DataServiceException, DataClientException;
|
||||
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
package com.microsoft.azure.kusto.data;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public class ClientFactory {
|
||||
|
||||
private ClientFactory() {
|
||||
// Hide the default constructor, as this is a factory with static methods
|
||||
}
|
||||
|
@ -43,12 +45,12 @@ public class ClientFactory {
|
|||
* customized with the given properties.
|
||||
*
|
||||
* @param csb the connection string builder
|
||||
* @param client CloseableHttpClient client. It will not be closed when {@link Client#close} is called.
|
||||
* @param client HttpClient client.
|
||||
* @return a fully constructed {@linkplain Client} instance
|
||||
* @throws URISyntaxException if the cluster URL is invalid
|
||||
*/
|
||||
public static Client createClient(ConnectionStringBuilder csb, CloseableHttpClient client) throws URISyntaxException {
|
||||
return client == null ? createClient(csb, (HttpClientProperties) null) : new ClientImpl(csb, client, true);
|
||||
public static Client createClient(ConnectionStringBuilder csb, HttpClient client) throws URISyntaxException {
|
||||
return client == null ? createClient(csb, (HttpClientProperties) null) : new ClientImpl(csb, client);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,7 +87,7 @@ public class ClientFactory {
|
|||
* @return a fully constructed {@linkplain StreamingClient} instance
|
||||
* @throws URISyntaxException if the cluster URL is invalid
|
||||
*/
|
||||
public static StreamingClient createStreamingClient(ConnectionStringBuilder csb, CloseableHttpClient httpClient) throws URISyntaxException {
|
||||
return new ClientImpl(csb, httpClient, true);
|
||||
public static StreamingClient createStreamingClient(ConnectionStringBuilder csb, HttpClient httpClient) throws URISyntaxException {
|
||||
return new ClientImpl(csb, httpClient);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,126 +3,57 @@
|
|||
|
||||
package com.microsoft.azure.kusto.data;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.core.http.HttpRequest;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import com.microsoft.azure.kusto.data.auth.CloudInfo;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import com.microsoft.azure.kusto.data.auth.TokenProviderBase;
|
||||
import com.microsoft.azure.kusto.data.auth.TokenProviderFactory;
|
||||
import com.microsoft.azure.kusto.data.auth.endpoints.KustoTrustedEndpoints;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.KustoClientInvalidConnectionStringException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.KustoServiceQueryError;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientFactory;
|
||||
import com.microsoft.azure.kusto.data.http.HttpPostUtils;
|
||||
import com.microsoft.azure.kusto.data.http.UncloseableStream;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.MonitoredActivity;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.SupplierTwoExceptions;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.TraceableAttributes;
|
||||
import com.microsoft.azure.kusto.data.exceptions.*;
|
||||
import com.microsoft.azure.kusto.data.http.*;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.*;
|
||||
import com.microsoft.azure.kusto.data.req.KustoRequest;
|
||||
import com.microsoft.azure.kusto.data.req.KustoRequestContext;
|
||||
import com.microsoft.azure.kusto.data.res.JsonResult;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.ParseException;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.entity.AbstractHttpEntity;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.InputStreamEntity;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class ClientImpl implements Client, StreamingClient {
|
||||
private static final String ADMIN_COMMANDS_PREFIX = ".";
|
||||
class ClientImpl extends BaseClient {
|
||||
public static final String MGMT_ENDPOINT_VERSION = "v1";
|
||||
public static final String QUERY_ENDPOINT_VERSION = "v2";
|
||||
public static final String STREAMING_VERSION = "v1";
|
||||
private static final String DEFAULT_DATABASE_NAME = "NetDefaultDb";
|
||||
private static final Long COMMAND_TIMEOUT_IN_MILLISECS = TimeUnit.MINUTES.toMillis(10);
|
||||
private static final Long QUERY_TIMEOUT_IN_MILLISECS = TimeUnit.MINUTES.toMillis(4);
|
||||
private static final Long STREAMING_INGEST_TIMEOUT_IN_MILLISECS = TimeUnit.MINUTES.toMillis(10);
|
||||
private static final int CLIENT_SERVER_DELTA_IN_MILLISECS = (int) TimeUnit.SECONDS.toMillis(30);
|
||||
public static final String CLIENT_VERSION_HEADER = "x-ms-client-version";
|
||||
public static final String APP_HEADER = "x-ms-app";
|
||||
public static final String USER_HEADER = "x-ms-user";
|
||||
public static final String FEDERATED_SECURITY_SUFFIX = ";fed=true";
|
||||
public static final String JAVA_INGEST_ACTIVITY_TYPE_PREFIX = "DN.JavaClient.Execute";
|
||||
|
||||
private final TokenProviderBase aadAuthenticationHelper;
|
||||
|
||||
private final String clusterUrl;
|
||||
private final ClientDetails clientDetails;
|
||||
private final CloseableHttpClient httpClient;
|
||||
private final boolean leaveHttpClientOpen;
|
||||
private boolean endpointValidated = false;
|
||||
|
||||
private final ObjectMapper objectMapper = Utils.getObjectMapper();
|
||||
|
||||
public ClientImpl(ConnectionStringBuilder csb) throws URISyntaxException {
|
||||
this(csb, HttpClientProperties.builder().build());
|
||||
}
|
||||
|
||||
public ClientImpl(ConnectionStringBuilder csb, HttpClientProperties properties) throws URISyntaxException {
|
||||
this(csb, HttpClientFactory.create(properties), false);
|
||||
this(csb, HttpClientFactory.create(properties));
|
||||
}
|
||||
|
||||
// Accepting a CloseableHttpClient so that we can create InputStream from response
|
||||
public ClientImpl(ConnectionStringBuilder csb, CloseableHttpClient httpClient, boolean leaveHttpClientOpen) throws URISyntaxException {
|
||||
URI clusterUrlForParsing = new URI(csb.getClusterUrl());
|
||||
String host = clusterUrlForParsing.getHost();
|
||||
Objects.requireNonNull(clusterUrlForParsing.getAuthority(), "clusterUri must have uri authority component");
|
||||
String auth = clusterUrlForParsing.getAuthority().toLowerCase();
|
||||
if (host == null) {
|
||||
host = StringUtils.removeEndIgnoreCase(auth, FEDERATED_SECURITY_SUFFIX);
|
||||
}
|
||||
URIBuilder uriBuilder = new URIBuilder().setScheme(clusterUrlForParsing.getScheme())
|
||||
.setHost(host);
|
||||
String path = clusterUrlForParsing.getPath();
|
||||
if (path != null && !path.isEmpty()) {
|
||||
path = StringUtils.removeEndIgnoreCase(path, FEDERATED_SECURITY_SUFFIX);
|
||||
path = StringUtils.removeEndIgnoreCase(path, "/");
|
||||
|
||||
uriBuilder.setPath(path);
|
||||
}
|
||||
|
||||
if (clusterUrlForParsing.getPort() != -1) {
|
||||
uriBuilder.setPort(clusterUrlForParsing.getPort());
|
||||
}
|
||||
csb.setClusterUrl(uriBuilder.build().toString());
|
||||
public ClientImpl(ConnectionStringBuilder csb, HttpClient httpClient) throws URISyntaxException {
|
||||
super(httpClient);
|
||||
String clusterURL = UriUtils.createClusterURLFrom(csb.getClusterUrl());
|
||||
csb.setClusterUrl(clusterURL);
|
||||
|
||||
clusterUrl = csb.getClusterUrl();
|
||||
aadAuthenticationHelper = clusterUrl.toLowerCase().startsWith(CloudInfo.LOCALHOST) ? null : TokenProviderFactory.createTokenProvider(csb, httpClient);
|
||||
clientDetails = new ClientDetails(csb.getApplicationNameForTracing(), csb.getUserNameForTracing(), csb.getClientVersionForTracing());
|
||||
this.httpClient = httpClient;
|
||||
this.leaveHttpClientOpen = leaveHttpClientOpen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KustoOperationResult execute(String command) throws DataServiceException, DataClientException {
|
||||
return execute(DEFAULT_DATABASE_NAME, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KustoOperationResult execute(String database, String command) throws DataServiceException, DataClientException {
|
||||
return execute(database, command, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KustoOperationResult execute(String database, String command, ClientRequestProperties properties) throws DataServiceException, DataClientException {
|
||||
return execute(database, command, properties, determineCommandType(command));
|
||||
}
|
||||
|
||||
private KustoOperationResult execute(String database, String command, ClientRequestProperties properties, CommandType commandType)
|
||||
throws DataServiceException, DataClientException {
|
||||
return MonitoredActivity.invoke(
|
||||
(SupplierTwoExceptions<KustoOperationResult, DataServiceException, DataClientException>) () -> executeImpl(database, command, properties,
|
||||
commandType),
|
||||
commandType.getActivityTypeSuffix().concat(".execute"),
|
||||
updateAndGetExecuteTracingAttributes(database, properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -157,6 +88,16 @@ class ClientImpl implements Client, StreamingClient {
|
|||
return execute(database, command, properties, CommandType.ADMIN_COMMAND);
|
||||
}
|
||||
|
||||
private KustoOperationResult execute(String database, String command, ClientRequestProperties properties, CommandType commandType)
|
||||
throws DataServiceException, DataClientException {
|
||||
KustoRequest kr = new KustoRequest(command, database, properties, commandType);
|
||||
|
||||
return MonitoredActivity.invoke(
|
||||
(SupplierTwoExceptions<KustoOperationResult, DataServiceException, DataClientException>) () -> executeImpl(kr),
|
||||
commandType.getActivityTypeSuffix().concat(".execute"),
|
||||
updateAndGetExecuteTracingAttributes(database, properties));
|
||||
}
|
||||
|
||||
private Map<String, String> updateAndGetExecuteTracingAttributes(String database, TraceableAttributes traceableAttributes) {
|
||||
Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put("cluster", clusterUrl);
|
||||
|
@ -167,21 +108,55 @@ class ClientImpl implements Client, StreamingClient {
|
|||
return attributes;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private KustoOperationResult executeImpl(String database, String command, ClientRequestProperties properties, CommandType commandType)
|
||||
throws DataServiceException, DataClientException {
|
||||
String response = executeToJsonResult(database, command, properties);
|
||||
String clusterEndpoint = String.format(commandType.getEndpoint(), clusterUrl);
|
||||
private KustoOperationResult executeImpl(KustoRequest kr) throws DataServiceException, DataClientException {
|
||||
String response = executeToJsonResult(kr);
|
||||
String clusterEndpoint = String.format(kr.getCommandType().getEndpoint(), clusterUrl);
|
||||
return processJsonResult(new JsonResult(response, clusterEndpoint));
|
||||
}
|
||||
|
||||
private KustoOperationResult processJsonResult(JsonResult res) throws DataServiceException, DataClientException {
|
||||
try {
|
||||
return new KustoOperationResult(response, clusterEndpoint.endsWith("v2/rest/query") ? "v2" : "v1");
|
||||
return new KustoOperationResult(res.getResult(), res.getEndpoint().endsWith("v2/rest/query") ? "v2" : "v1");
|
||||
} catch (KustoServiceQueryError e) {
|
||||
throw new DataServiceException(clusterEndpoint,
|
||||
throw new DataServiceException(res.getEndpoint(),
|
||||
"Error found while parsing json response as KustoOperationResult:" + e.getMessage(), e, e.isPermanent());
|
||||
} catch (Exception e) {
|
||||
throw new DataClientException(clusterEndpoint, e.getMessage(), e);
|
||||
throw new DataClientException(res.getEndpoint(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
KustoRequestContext prepareRequest(@NotNull KustoRequest kr) throws DataServiceException, DataClientException {
|
||||
|
||||
// Validate and optimize the query object
|
||||
kr.validateAndOptimize();
|
||||
|
||||
String clusterEndpoint = String.format(kr.getCommandType().getEndpoint(), clusterUrl);
|
||||
String authorization = getAuthorizationHeaderValue();
|
||||
|
||||
// Validate the endpoint (?)
|
||||
validateEndpoint();
|
||||
|
||||
// Build the tracing object
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(kr.getProperties())
|
||||
.withRequestPrefix("KJC.execute")
|
||||
.withActivitySuffix(kr.getCommandType().getActivityTypeSuffix())
|
||||
.withClientDetails(clientDetails)
|
||||
.build();
|
||||
|
||||
// Build the HTTP request
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost(clusterEndpoint)
|
||||
.createCommandPayload(kr)
|
||||
.withTracing(tracing)
|
||||
.withAuthorization(authorization)
|
||||
.build();
|
||||
|
||||
// Wrap the Http request and SDK request in a singular object, so we can use BiConsumer later.
|
||||
return new KustoRequestContext(kr, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String executeToJsonResult(String command) throws DataServiceException, DataClientException {
|
||||
return executeToJsonResult(DEFAULT_DATABASE_NAME, command);
|
||||
|
@ -194,39 +169,21 @@ class ClientImpl implements Client, StreamingClient {
|
|||
|
||||
@Override
|
||||
public String executeToJsonResult(String database, String command, ClientRequestProperties properties) throws DataServiceException, DataClientException {
|
||||
// Argument validation
|
||||
if (StringUtils.isEmpty(database)) {
|
||||
throw new IllegalArgumentException("Database is empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(command)) {
|
||||
throw new IllegalArgumentException("Command is empty");
|
||||
}
|
||||
command = command.trim();
|
||||
CommandType commandType = determineCommandType(command);
|
||||
long timeoutMs = determineTimeout(properties, commandType, clusterUrl);
|
||||
// TODO save the uri once - no need to format everytime
|
||||
String clusterEndpoint = String.format(commandType.getEndpoint(), clusterUrl);
|
||||
|
||||
Map<String, String> headers;
|
||||
|
||||
try {
|
||||
headers = generateIngestAndCommandHeaders(properties, "KJC.execute",
|
||||
commandType.getActivityTypeSuffix());
|
||||
validateEndpoint();
|
||||
} catch (KustoClientInvalidConnectionStringException e) {
|
||||
throw new DataClientException(clusterUrl, e.getMessage(), e);
|
||||
}
|
||||
addCommandHeaders(headers);
|
||||
String jsonPayload = generateCommandPayload(database, command, properties);
|
||||
StringEntity requestEntity = new StringEntity(jsonPayload, ContentType.APPLICATION_JSON);
|
||||
// trace execution
|
||||
return MonitoredActivity.invoke(
|
||||
(SupplierTwoExceptions<String, DataServiceException, DataClientException>) () -> HttpPostUtils.post(httpClient, clusterEndpoint, requestEntity,
|
||||
timeoutMs + CLIENT_SERVER_DELTA_IN_MILLISECS, headers),
|
||||
commandType.getActivityTypeSuffix().concat(".executeToJsonResult"));
|
||||
KustoRequest kr = new KustoRequest(command, database, properties);
|
||||
return executeToJsonResult(kr);
|
||||
}
|
||||
|
||||
private void validateEndpoint() throws DataServiceException, KustoClientInvalidConnectionStringException {
|
||||
private String executeToJsonResult(KustoRequest kr) throws DataServiceException, DataClientException {
|
||||
|
||||
KustoRequestContext request = prepareRequest(kr);
|
||||
|
||||
// Get the response and trace the call
|
||||
return MonitoredActivity.invoke(
|
||||
(SupplierOneException<String, DataServiceException>) () -> post(request.getHttpRequest()),
|
||||
request.getSdkRequest().getCommandType().getActivityTypeSuffix().concat(".executeToJsonResult"));
|
||||
}
|
||||
|
||||
private void validateEndpoint() throws DataServiceException, DataClientException {
|
||||
if (!endpointValidated) {
|
||||
KustoTrustedEndpoints.validateTrustedEndpoint(clusterUrl,
|
||||
CloudInfo.retrieveCloudInfoForCluster(clusterUrl).getLoginEndpoint());
|
||||
|
@ -260,52 +217,71 @@ class ClientImpl implements Client, StreamingClient {
|
|||
}
|
||||
|
||||
private KustoOperationResult executeStreamingIngestImpl(String clusterEndpoint, InputStream stream, String blobUrl, ClientRequestProperties properties,
|
||||
boolean leaveOpen)
|
||||
throws DataServiceException, DataClientException {
|
||||
boolean leaveOpen) throws DataServiceException, DataClientException {
|
||||
boolean isStreamSource = stream != null;
|
||||
Map<String, String> headers = generateIngestAndCommandHeaders(properties,
|
||||
"KJC.executeStreamingIngest" + (isStreamSource ? "" : "FromBlob"),
|
||||
CommandType.STREAMING_INGEST.getActivityTypeSuffix());
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
String authorization = getAuthorizationHeaderValue();
|
||||
String contentEncoding = null;
|
||||
String contentType;
|
||||
if (isStreamSource) {
|
||||
headers.put(HttpHeaders.CONTENT_ENCODING, "gzip");
|
||||
contentEncoding = "gzip";
|
||||
}
|
||||
|
||||
Long timeoutMs = populateHeadersAndGetTimeout(properties, headers);
|
||||
try (InputStream ignored = (isStreamSource && !leaveOpen) ? stream : null) {
|
||||
validateEndpoint();
|
||||
|
||||
// We use UncloseableStream to prevent HttpClient From closing it
|
||||
AbstractHttpEntity entity = isStreamSource ? new InputStreamEntity(new UncloseableStream(stream))
|
||||
: new StringEntity(new IngestionSourceStorage(blobUrl).toString(), ContentType.APPLICATION_JSON);
|
||||
String response;
|
||||
// trace executeStreamingIngest
|
||||
response = MonitoredActivity.invoke(
|
||||
(SupplierTwoExceptions<String, DataServiceException, DataClientException>) () -> HttpPostUtils.post(httpClient, clusterEndpoint, entity,
|
||||
timeoutMs + CLIENT_SERVER_DELTA_IN_MILLISECS, headers),
|
||||
"ClientImpl.executeStreamingIngest");
|
||||
return new KustoOperationResult(response, "v1");
|
||||
} catch (KustoServiceQueryError e) {
|
||||
throw new DataClientException(clusterEndpoint, "Error converting json response to KustoOperationResult:" + e.getMessage(), e);
|
||||
} catch (KustoClientInvalidConnectionStringException | IOException e) {
|
||||
throw new DataClientException(clusterUrl, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Long populateHeadersAndGetTimeout(ClientRequestProperties properties, Map<String, String> headers) throws DataClientException {
|
||||
Long timeoutMs = null;
|
||||
// This was a separate method but was moved into the body of this method because it performs a side effect
|
||||
if (properties != null) {
|
||||
timeoutMs = determineTimeout(properties, CommandType.STREAMING_INGEST, clusterUrl);
|
||||
Iterator<Map.Entry<String, Object>> iterator = properties.getOptions();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Object> pair = iterator.next();
|
||||
headers.put(pair.getKey(), pair.getValue().toString());
|
||||
}
|
||||
}
|
||||
if (timeoutMs == null) {
|
||||
timeoutMs = STREAMING_INGEST_TIMEOUT_IN_MILLISECS;
|
||||
}
|
||||
|
||||
return timeoutMs;
|
||||
try (InputStream ignored = (isStreamSource && !leaveOpen) ? stream : null) {
|
||||
// Validate the endpoint
|
||||
validateEndpoint();
|
||||
BinaryData data;
|
||||
|
||||
if (isStreamSource) {
|
||||
// We use UncloseableStream to prevent HttpClient From closing it
|
||||
data = BinaryData.fromStream(new UncloseableStream(stream));
|
||||
contentType = "application/octet-stream";
|
||||
} else {
|
||||
data = BinaryData.fromString(new IngestionSourceStorage(blobUrl).toString());
|
||||
contentType = "application/json";
|
||||
}
|
||||
|
||||
// Build the tracing object
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(properties)
|
||||
.withRequestPrefix("KJC.executeStreamingIngest" + (isStreamSource ? "" : "FromBlob"))
|
||||
.withActivitySuffix(CommandType.STREAMING_INGEST.getActivityTypeSuffix())
|
||||
.withClientDetails(clientDetails)
|
||||
.build();
|
||||
|
||||
// Build the HTTP request. Since this is an ingestion and not a command, content headers aren't auto-applied.
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost(clusterEndpoint)
|
||||
.withTracing(tracing)
|
||||
.withHeaders(headers)
|
||||
.withAuthorization(authorization)
|
||||
.withContentType(contentType)
|
||||
.withContentEncoding(contentEncoding)
|
||||
.withBody(data)
|
||||
.build();
|
||||
|
||||
// Get the response, and trace the call.
|
||||
String response = MonitoredActivity.invoke(
|
||||
(SupplierOneException<String, DataServiceException>) () -> post(request), "ClientImpl.executeStreamingIngest");
|
||||
|
||||
return new KustoOperationResult(response, "v1");
|
||||
|
||||
} catch (KustoServiceQueryError e) {
|
||||
throw new DataClientException(clusterEndpoint, "Error converting json response to KustoOperationResult:" + e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
throw new DataClientException(clusterUrl, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildClusterEndpoint(String database, String table, String format, String mappingName) {
|
||||
|
@ -339,140 +315,57 @@ class ClientImpl implements Client, StreamingClient {
|
|||
@Override
|
||||
public InputStream executeStreamingQuery(String database, String command, ClientRequestProperties properties)
|
||||
throws DataServiceException, DataClientException {
|
||||
if (StringUtils.isEmpty(database)) {
|
||||
throw new IllegalArgumentException("Database is empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(command)) {
|
||||
throw new IllegalArgumentException("Command is empty");
|
||||
}
|
||||
command = command.trim();
|
||||
CommandType commandType = determineCommandType(command);
|
||||
long timeoutMs = determineTimeout(properties, commandType, clusterUrl);
|
||||
String clusterEndpoint = String.format(commandType.getEndpoint(), clusterUrl);
|
||||
KustoRequest kr = new KustoRequest(command, database, properties);
|
||||
return executeStreamingQuery(kr);
|
||||
}
|
||||
|
||||
Map<String, String> headers;
|
||||
headers = generateIngestAndCommandHeaders(properties, "KJC.executeStreaming",
|
||||
commandType.getActivityTypeSuffix());
|
||||
private InputStream executeStreamingQuery(@NotNull KustoRequest kr) throws DataServiceException, DataClientException {
|
||||
|
||||
addCommandHeaders(headers);
|
||||
String jsonPayload = generateCommandPayload(database, command, properties);
|
||||
// Validate and optimize the query object
|
||||
kr.validateAndOptimize();
|
||||
|
||||
try {
|
||||
validateEndpoint();
|
||||
} catch (KustoClientInvalidConnectionStringException e) {
|
||||
throw new DataClientException(clusterUrl, e.getMessage(), e);
|
||||
}
|
||||
String clusterEndpoint = String.format(kr.getCommandType().getEndpoint(), clusterUrl);
|
||||
String authorization = getAuthorizationHeaderValue();
|
||||
|
||||
// trace httpCall
|
||||
// Validate the endpoint
|
||||
validateEndpoint();
|
||||
|
||||
// Build the tracing object
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(kr.getProperties())
|
||||
.withRequestPrefix("KJC.executeStreaming")
|
||||
.withActivitySuffix(kr.getCommandType().getActivityTypeSuffix())
|
||||
.withClientDetails(clientDetails)
|
||||
.build();
|
||||
|
||||
// Build the HTTP request
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost(clusterEndpoint)
|
||||
.createCommandPayload(kr)
|
||||
.withTracing(tracing)
|
||||
.withAuthorization(authorization)
|
||||
.build();
|
||||
|
||||
// Get the response and trace the call
|
||||
return MonitoredActivity.invoke(
|
||||
(SupplierTwoExceptions<InputStream, DataServiceException, DataClientException>) () -> HttpPostUtils.postToStreamingOutput(httpClient,
|
||||
clusterEndpoint,
|
||||
jsonPayload, timeoutMs + CLIENT_SERVER_DELTA_IN_MILLISECS, headers, properties.getRedirectCount()),
|
||||
"ClientImpl.executeStreamingQuery", updateAndGetExecuteTracingAttributes(database, properties));
|
||||
(SupplierOneException<InputStream, DataServiceException>) () -> postToStreamingOutput(request, kr.getProperties().getRedirectCount()),
|
||||
"ClientImpl.executeStreamingQuery", updateAndGetExecuteTracingAttributes(kr.getDatabase(), kr.getProperties()));
|
||||
}
|
||||
|
||||
private long determineTimeout(ClientRequestProperties properties, CommandType commandType, String clusterUrl) throws DataClientException {
|
||||
Long timeoutMs;
|
||||
try {
|
||||
timeoutMs = properties == null ? null : properties.getTimeoutInMilliSec();
|
||||
} catch (ParseException e) {
|
||||
throw new DataClientException(clusterUrl, "Failed to parse timeout from ClientRequestProperties");
|
||||
}
|
||||
if (timeoutMs == null) {
|
||||
if (commandType == CommandType.ADMIN_COMMAND) {
|
||||
timeoutMs = COMMAND_TIMEOUT_IN_MILLISECS;
|
||||
} else {
|
||||
timeoutMs = QUERY_TIMEOUT_IN_MILLISECS;
|
||||
}
|
||||
}
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
private CommandType determineCommandType(String command) {
|
||||
if (command.startsWith(ADMIN_COMMANDS_PREFIX)) {
|
||||
return CommandType.ADMIN_COMMAND;
|
||||
}
|
||||
return CommandType.QUERY;
|
||||
}
|
||||
|
||||
private Map<String, String> generateIngestAndCommandHeaders(ClientRequestProperties properties,
|
||||
String clientRequestIdPrefix,
|
||||
String activityTypeSuffix)
|
||||
throws DataServiceException, DataClientException {
|
||||
|
||||
Map<String, String> headers = extractTracingHeaders(properties);
|
||||
|
||||
private String getAuthorizationHeaderValue() throws DataServiceException, DataClientException {
|
||||
if (aadAuthenticationHelper != null) {
|
||||
headers.put(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", aadAuthenticationHelper.acquireAccessToken()));
|
||||
return String.format("Bearer %s", aadAuthenticationHelper.acquireAccessToken());
|
||||
}
|
||||
|
||||
String clientRequestId;
|
||||
if (properties != null && StringUtils.isNotBlank(properties.getClientRequestId())) {
|
||||
clientRequestId = properties.getClientRequestId();
|
||||
} else {
|
||||
clientRequestId = String.format("%s;%s", clientRequestIdPrefix, UUID.randomUUID());
|
||||
}
|
||||
|
||||
headers.put("x-ms-client-request-id", clientRequestId);
|
||||
|
||||
headers.put("Connection", "Keep-Alive");
|
||||
|
||||
UUID activityId = UUID.randomUUID();
|
||||
String activityContext = String.format("%s%s/%s, ActivityId=%s, ParentId=%s, ClientRequestId=%s", JAVA_INGEST_ACTIVITY_TYPE_PREFIX, activityTypeSuffix,
|
||||
activityId, activityId, activityId, clientRequestId);
|
||||
headers.put("x-ms-activitycontext", activityContext);
|
||||
|
||||
// replace non-ascii characters in header values with '?'
|
||||
headers.replaceAll((_i, v) -> v == null ? null : v.replaceAll("[^\\x00-\\x7F]", "?"));
|
||||
return headers;
|
||||
}
|
||||
|
||||
Map<String, String> extractTracingHeaders(ClientRequestProperties properties) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
String version = clientDetails.getClientVersionForTracing();
|
||||
if (StringUtils.isNotBlank(version)) {
|
||||
headers.put(CLIENT_VERSION_HEADER, version);
|
||||
}
|
||||
|
||||
String app = (properties == null || properties.getApplication() == null) ? clientDetails.getApplicationForTracing() : properties.getApplication();
|
||||
if (StringUtils.isNotBlank(app)) {
|
||||
headers.put(APP_HEADER, app);
|
||||
}
|
||||
|
||||
String user = (properties == null || properties.getUser() == null) ? clientDetails.getUserNameForTracing() : properties.getUser();
|
||||
if (StringUtils.isNotBlank(user)) {
|
||||
headers.put(USER_HEADER, user);
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String generateCommandPayload(String database, String command, ClientRequestProperties properties) {
|
||||
|
||||
ObjectNode json = objectMapper.createObjectNode()
|
||||
.put("db", database)
|
||||
.put("csl", command);
|
||||
|
||||
if (properties != null) {
|
||||
json.put("properties", properties.toString());
|
||||
}
|
||||
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
private void addCommandHeaders(Map<String, String> headers) {
|
||||
headers.put(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getClusterUrl() {
|
||||
return clusterUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!leaveHttpClientOpen) {
|
||||
httpClient.close();
|
||||
}
|
||||
ClientDetails getClientDetails() {
|
||||
return clientDetails;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -341,17 +341,6 @@ public class ClientRequestProperties implements Serializable, TraceableAttribute
|
|||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of time a query may execute on the service before it times out, formatted as a KQL timespan.
|
||||
* @param timeoutObj amount of time before timeout, which may be a Long, String or Integer.
|
||||
* Value must be between 1 minute and 1 hour, and so value below the minimum or above the maximum will be adjusted accordingly.
|
||||
* @Deprecated use {@link #getTimeoutAsCslTimespan(Object)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
String getTimeoutAsString(Object timeoutObj) {
|
||||
return getTimeoutAsCslTimespan(timeoutObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of time a query may execute on the service before it times out, formatted as a KQL timespan.
|
||||
* @param timeoutObj amount of time before timeout, which may be a Long, String or Integer.
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.microsoft.azure.kusto.data;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microsoft.azure.kusto.data.http.HttpPostUtils;
|
||||
|
||||
class IngestionSourceStorage {
|
||||
public String sourceUri;
|
||||
|
@ -12,9 +10,8 @@ class IngestionSourceStorage {
|
|||
}
|
||||
|
||||
public String toString() {
|
||||
ObjectMapper objectMapper = Utils.getObjectMapper();
|
||||
try {
|
||||
return objectMapper.writeValueAsString(this);
|
||||
return Utils.getObjectMapper().writeValueAsString(this);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
@ -6,10 +6,9 @@ package com.microsoft.azure.kusto.data;
|
|||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface StreamingClient extends Closeable {
|
||||
public interface StreamingClient {
|
||||
/**
|
||||
* <p>Ingest data from a given stream directly into Kusto database.</p>
|
||||
* This method ingests the data from a given stream directly into Kusto database, using streaming ingestion endpoint,
|
||||
|
@ -49,16 +48,6 @@ public interface StreamingClient extends Closeable {
|
|||
|
||||
InputStream executeStreamingQuery(String command) throws DataServiceException, DataClientException;
|
||||
|
||||
/**
|
||||
* <p>Execute the provided command against the default database.</p>
|
||||
*
|
||||
* @param command The command to execute
|
||||
* @return {@link KustoOperationResult} object including the ingestion result
|
||||
* @throws DataClientException An exception originating from a client activity
|
||||
* @throws DataServiceException An exception returned from the service
|
||||
*/
|
||||
KustoOperationResult execute(String command) throws DataServiceException, DataClientException;
|
||||
|
||||
KustoOperationResult executeStreamingIngestFromBlob(String databaseName, String tableName, String blobUrl, ClientRequestProperties clientRequestProperties,
|
||||
String dataFormat, String ingestionMappingReference) throws DataServiceException, DataClientException;
|
||||
}
|
||||
|
|
|
@ -4,14 +4,44 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.apache.http.client.utils.URIBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
|
||||
public class UriUtils {
|
||||
|
||||
public static final String FEDERATED_SECURITY_SUFFIX = ";fed=true";
|
||||
|
||||
private UriUtils() {
|
||||
// Providing hidden constructor to hide default public constructor in utils class
|
||||
}
|
||||
|
||||
public static String createClusterURLFrom(final String clusterURI) throws URISyntaxException {
|
||||
URI clusterUrlForParsing = new URI(clusterURI);
|
||||
String host = clusterUrlForParsing.getHost();
|
||||
Objects.requireNonNull(clusterUrlForParsing.getAuthority(), "clusterUri must have uri authority component");
|
||||
String auth = clusterUrlForParsing.getAuthority().toLowerCase();
|
||||
if (host == null) {
|
||||
host = StringUtils.removeEndIgnoreCase(auth, FEDERATED_SECURITY_SUFFIX);
|
||||
}
|
||||
URIBuilder uriBuilder = new URIBuilder()
|
||||
.setScheme(clusterUrlForParsing.getScheme())
|
||||
.setHost(host);
|
||||
String path = clusterUrlForParsing.getPath();
|
||||
if (path != null && !path.isEmpty()) {
|
||||
path = StringUtils.removeEndIgnoreCase(path, FEDERATED_SECURITY_SUFFIX);
|
||||
path = StringUtils.removeEndIgnoreCase(path, "/");
|
||||
|
||||
uriBuilder.setPath(path);
|
||||
}
|
||||
|
||||
if (clusterUrlForParsing.getPort() != -1) {
|
||||
uriBuilder.setPort(clusterUrlForParsing.getPort());
|
||||
}
|
||||
return uriBuilder.build().toString();
|
||||
}
|
||||
|
||||
public static String setPathForUri(String uri, String path, boolean ensureTrailingSlash) throws URISyntaxException {
|
||||
path = StringUtils.prependIfMissing(path, "/");
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package com.microsoft.azure.kusto.data;
|
||||
|
||||
import com.azure.core.http.HttpHeader;
|
||||
import com.azure.core.http.HttpHeaderName;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.azure.core.implementation.StringBuilderWriter;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
|
@ -15,13 +19,18 @@ import javax.net.ssl.SSLException;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.Writer;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.zip.DeflaterInputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class Utils {
|
||||
private static final int MAX_RETRY_ATTEMPTS = 4;
|
||||
|
@ -111,4 +120,59 @@ public class Utils {
|
|||
.retryOnException(predicate)
|
||||
.build();
|
||||
}
|
||||
|
||||
// TODO Copied from apache IoUtils - should we take it back ? don't recall why removed
|
||||
public static String gzipedInputToString(InputStream in) {
|
||||
try (GZIPInputStream gz = new GZIPInputStream(in)) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
try (StringBuilderWriter sw = new StringBuilderWriter(stringBuilder)) {
|
||||
copy(gz, sw);
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an HTTP response is GZIP compressed.
|
||||
* @param response The HTTP response to check
|
||||
* @return a boolean indicating if the CONTENT_ENCODING header contains "gzip"
|
||||
*/
|
||||
public static boolean isGzipResponse(HttpResponse response) {
|
||||
Optional<HttpHeader> contentEncoding = Optional.ofNullable(response.getHeaders().get(HttpHeaderName.CONTENT_ENCODING));
|
||||
return contentEncoding
|
||||
.filter(header -> header.getValue().contains("gzip"))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an HTTP response body as an InputStream.
|
||||
* @param response The response object to convert to an InputStream
|
||||
* @return The response body as an InputStream
|
||||
* @throws IOException An exception indicating an IO failure
|
||||
*/
|
||||
public static InputStream getResponseAsStream(HttpResponse response) throws IOException {
|
||||
InputStream contentStream = response.getBodyAsBinaryData().toStream();
|
||||
String contentEncoding = response.getHeaders().get(HttpHeaderName.CONTENT_ENCODING).getValue();
|
||||
if (contentEncoding.contains("gzip")) {
|
||||
return new GZIPInputStream(contentStream);
|
||||
} else if (contentEncoding.contains("deflate")) {
|
||||
return new DeflaterInputStream(contentStream);
|
||||
}
|
||||
return contentStream;
|
||||
}
|
||||
|
||||
public static int copy(final InputStream input, final Writer writer)
|
||||
throws IOException {
|
||||
final InputStreamReader reader = new InputStreamReader(input);
|
||||
final char[] buffer = new char[1024];
|
||||
int count = 0;
|
||||
int n;
|
||||
while (-1 != (n = reader.read(buffer))) {
|
||||
writer.write(buffer, 0, n);
|
||||
count += n;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import com.microsoft.aad.msal4j.IConfidentialClientApplication;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import com.microsoft.aad.msal4j.IConfidentialClientApplication;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.microsoft.azure.kusto.data.auth;
|
|||
|
||||
import com.azure.core.credential.AccessToken;
|
||||
import com.azure.core.credential.TokenRequestContext;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.identity.AzureCliCredential;
|
||||
import com.azure.identity.AzureCliCredentialBuilder;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
package com.microsoft.azure.kusto.data.auth;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.microsoft.azure.kusto.data.auth;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CallbackTokenProviderFunction {
|
||||
|
|
|
@ -3,17 +3,16 @@
|
|||
|
||||
package com.microsoft.azure.kusto.data.auth;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.SupplierTwoExceptions;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.microsoft.azure.kusto.data.instrumentation.MonitoredActivity;
|
||||
import org.apache.http.client.HttpClient;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.microsoft.azure.kusto.data.auth;
|
||||
|
||||
import com.azure.core.http.*;
|
||||
import com.azure.core.util.Context;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
@ -7,27 +9,21 @@ import com.microsoft.azure.kusto.data.ExponentialRetry;
|
|||
import com.microsoft.azure.kusto.data.Utils;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientFactory;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.SupplierOneException;
|
||||
import com.microsoft.azure.kusto.data.UriUtils;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import com.microsoft.azure.kusto.data.http.HttpStatus;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.SupplierOneException;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.TraceableAttributes;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.MonitoredActivity;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.*;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CloudInfo implements TraceableAttributes, Serializable {
|
||||
private static final Map<String, CloudInfo> cache = new HashMap<>();
|
||||
|
@ -114,7 +110,6 @@ public class CloudInfo implements TraceableAttributes, Serializable {
|
|||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,46 +117,46 @@ public class CloudInfo implements TraceableAttributes, Serializable {
|
|||
CloudInfo result;
|
||||
HttpClient localHttpClient = givenHttpClient == null ? HttpClientFactory.create(null) : givenHttpClient;
|
||||
try {
|
||||
HttpGet request = new HttpGet(UriUtils.appendPathToUri(clusterUrl, METADATA_ENDPOINT));
|
||||
request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip,deflate");
|
||||
request.addHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
HttpRequest request = new HttpRequest(HttpMethod.GET, UriUtils.appendPathToUri(clusterUrl, METADATA_ENDPOINT));
|
||||
request.setHeader(HttpHeaderName.ACCEPT_ENCODING, "gzip,deflate");
|
||||
request.setHeader(HttpHeaderName.ACCEPT, "application/json");
|
||||
|
||||
// trace CloudInfo.httpCall
|
||||
HttpResponse response = MonitoredActivity.invoke(
|
||||
(SupplierOneException<HttpResponse, IOException>) () -> localHttpClient.execute(request),
|
||||
"CloudInfo.httpCall");
|
||||
try {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
String content = EntityUtils.toString(response.getEntity());
|
||||
// Fixme: Make this async in the future
|
||||
try (HttpResponse response = MonitoredActivity.invoke(
|
||||
(SupplierOneException<HttpResponse, IOException>) () -> localHttpClient.sendSync(request, Context.NONE),
|
||||
"CloudInfo.httpCall")) {
|
||||
int statusCode = response.getStatusCode();
|
||||
if (statusCode == HttpStatus.OK) {
|
||||
String content = null;
|
||||
if (Utils.isGzipResponse(response)) {
|
||||
content = Utils.gzipedInputToString(response.getBodyAsBinaryData().toStream());
|
||||
} else {
|
||||
content = response.getBodyAsBinaryData().toString();
|
||||
}
|
||||
if (content == null || content.equals("") || content.equals("{}")) {
|
||||
throw new DataServiceException(clusterUrl, "Error in metadata endpoint, received no data", true);
|
||||
}
|
||||
result = parseCloudInfo(content);
|
||||
} else if (statusCode == 404) {
|
||||
} else if (statusCode == HttpStatus.NOT_FOUND) {
|
||||
result = DEFAULT_CLOUD;
|
||||
} else {
|
||||
String errorFromResponse = EntityUtils.toString(response.getEntity());
|
||||
String errorFromResponse = response.getBodyAsBinaryData().toString();
|
||||
if (errorFromResponse.isEmpty()) {
|
||||
errorFromResponse = response.getStatusLine().getReasonPhrase();
|
||||
// Fixme: Missing reason phrase to add to exception. Potentially want to use an enum.
|
||||
errorFromResponse = "";
|
||||
}
|
||||
throw new DataServiceException(clusterUrl,
|
||||
"Error in metadata endpoint, got code: " + statusCode + "\nWith error: " + errorFromResponse,
|
||||
statusCode != HttpStatus.SC_TOO_MANY_REQUESTS);
|
||||
}
|
||||
} finally {
|
||||
if (response instanceof Closeable) {
|
||||
((Closeable) response).close();
|
||||
throw new DataServiceException(clusterUrl, "Error in metadata endpoint, got code: " + statusCode +
|
||||
"\nWith error: " + errorFromResponse, statusCode != HttpStatus.TOO_MANY_REQS);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (givenHttpClient == null && localHttpClient != null) {
|
||||
if (givenHttpClient == null && localHttpClient instanceof Closeable) {
|
||||
((Closeable) localHttpClient).close();
|
||||
}
|
||||
}
|
||||
cache.put(clusterUrl, result);
|
||||
return result;
|
||||
// });
|
||||
}
|
||||
|
||||
private static CloudInfo parseCloudInfo(String content) throws JsonProcessingException {
|
||||
|
|
|
@ -17,7 +17,7 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.microsoft.azure.kusto.data.auth;
|
|||
|
||||
import com.microsoft.aad.msal4j.*;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -1,26 +1,14 @@
|
|||
package com.microsoft.azure.kusto.data.auth;
|
||||
|
||||
import com.azure.core.http.HttpRequest;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.azure.core.http.*;
|
||||
import com.azure.core.util.Context;
|
||||
import com.azure.core.util.CoreUtils;
|
||||
import com.microsoft.aad.msal4j.IHttpClient;
|
||||
import com.microsoft.aad.msal4j.IHttpResponse;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.*;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.InputStreamEntity;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This class wraps both of the azure http client interfaces - IHttpClient and HttpClient to use our apache http client.
|
||||
|
@ -28,9 +16,8 @@ import java.util.concurrent.Executors;
|
|||
* HttpClient is synchronous, so the implementation is straight-forward.
|
||||
* IHttpClient is asynchronous, so we need to be more clever about integrating it with the synchronous apache client.
|
||||
*/
|
||||
public class HttpClientWrapper implements com.azure.core.http.HttpClient, IHttpClient {
|
||||
public class HttpClientWrapper implements HttpClient, IHttpClient {
|
||||
|
||||
private static final Executor EXECUTOR = Executors.newCachedThreadPool();
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpClientWrapper(HttpClient httpClient) {
|
||||
|
@ -40,93 +27,7 @@ public class HttpClientWrapper implements com.azure.core.http.HttpClient, IHttpC
|
|||
// Implementation of the asynchronous IHttpClient
|
||||
@Override
|
||||
public Mono<HttpResponse> send(HttpRequest httpRequest) {
|
||||
// Creating the apache request
|
||||
|
||||
HttpUriRequest request;
|
||||
String uri = httpRequest.getUrl().toString();
|
||||
switch (httpRequest.getHttpMethod()) {
|
||||
case GET:
|
||||
request = new HttpGet(uri);
|
||||
break;
|
||||
case POST:
|
||||
request = new HttpPost(uri);
|
||||
break;
|
||||
case PUT:
|
||||
request = new HttpPut(uri);
|
||||
break;
|
||||
case PATCH:
|
||||
request = new HttpPatch(uri);
|
||||
break;
|
||||
case DELETE:
|
||||
request = new HttpDelete(uri);
|
||||
break;
|
||||
case HEAD:
|
||||
request = new HttpHead(uri);
|
||||
break;
|
||||
case OPTIONS:
|
||||
request = new HttpOptions(uri);
|
||||
break;
|
||||
case TRACE:
|
||||
request = new HttpTrace(uri);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported HTTP method: " + httpRequest.getHttpMethod());
|
||||
}
|
||||
// Translating the headers
|
||||
|
||||
request.setHeaders(httpRequest.getHeaders().stream().filter(h -> isNotContentLength(h.getName())).map(h -> new BasicHeader(h.getName(), h.getValue()))
|
||||
.toArray(Header[]::new));
|
||||
|
||||
// Setting the request's body/entity
|
||||
|
||||
// This empty operation
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
HttpEntityEnclosingRequest entityEnclosingRequest = (HttpEntityEnclosingRequest) request;
|
||||
PipedOutputStream osPipe = new PipedOutputStream();
|
||||
PipedInputStream isPipe = null;
|
||||
try {
|
||||
isPipe = new PipedInputStream(osPipe);
|
||||
} catch (IOException ignored) {
|
||||
// This should never happen
|
||||
}
|
||||
|
||||
EXECUTOR.execute(() -> {
|
||||
httpRequest.getBody().publishOn(Schedulers.boundedElastic()).map(buf -> {
|
||||
try {
|
||||
if (buf.hasArray()) {
|
||||
osPipe.write(buf.array(), buf.position(), buf.remaining());
|
||||
} else {
|
||||
byte[] bytes = new byte[buf.remaining()];
|
||||
buf.get(bytes);
|
||||
osPipe.write(bytes);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).blockLast();
|
||||
try {
|
||||
osPipe.close();
|
||||
} catch (IOException e) {
|
||||
// This should never happen
|
||||
}
|
||||
});
|
||||
|
||||
String contentLength = httpRequest.getHeaders().getValue("Content-Length");
|
||||
String contentType = httpRequest.getHeaders().getValue("Content-Type");
|
||||
entityEnclosingRequest.setEntity(new InputStreamEntity(isPipe, contentLength == null ? -1 : Long.parseLong(contentLength),
|
||||
contentType == null ? null : ContentType.parse(contentType)));
|
||||
}
|
||||
|
||||
// The types of the Monos are different, but we ignore the results anyway (since we only care about the input stream) so this is fine.
|
||||
return Mono.create(monoSink -> {
|
||||
try {
|
||||
org.apache.http.HttpResponse response = httpClient.execute(request);
|
||||
monoSink.success(new HttpResponseWrapper(httpRequest, response));
|
||||
} catch (IOException e) {
|
||||
monoSink.error(e);
|
||||
}
|
||||
});
|
||||
return httpClient.send(httpRequest);
|
||||
}
|
||||
|
||||
private static boolean isNotContentLength(String name) {
|
||||
|
@ -135,34 +36,32 @@ public class HttpClientWrapper implements com.azure.core.http.HttpClient, IHttpC
|
|||
|
||||
// Implementation of the synchronous HttpClient
|
||||
@Override
|
||||
public IHttpResponse send(com.microsoft.aad.msal4j.HttpRequest httpRequest) throws Exception {
|
||||
HttpUriRequest request;
|
||||
|
||||
// Translating the request
|
||||
|
||||
String uri = httpRequest.url().toString();
|
||||
public IHttpResponse send(com.microsoft.aad.msal4j.HttpRequest httpRequest) {
|
||||
HttpMethod method;
|
||||
switch (httpRequest.httpMethod()) {
|
||||
case GET:
|
||||
request = new HttpGet(uri);
|
||||
method = HttpMethod.GET;
|
||||
break;
|
||||
case POST:
|
||||
request = new HttpPost(uri);
|
||||
method = HttpMethod.POST;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported HTTP method: " + httpRequest.httpMethod());
|
||||
}
|
||||
// Translating the headers
|
||||
|
||||
request.setHeaders(httpRequest.headers().entrySet().stream().filter(h -> isNotContentLength(h.getKey()))
|
||||
.map(h -> new BasicHeader(h.getKey(), h.getValue())).toArray(Header[]::new));
|
||||
|
||||
// Setting the request's body/entity
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
HttpEntityEnclosingRequest entityEnclosingRequest = (HttpEntityEnclosingRequest) request;
|
||||
String body = httpRequest.body();
|
||||
entityEnclosingRequest.setEntity(new ByteArrayEntity(body.getBytes(StandardCharsets.UTF_8)));
|
||||
// Generate an azure core HttpRequest from the existing msal4j HttpRequest
|
||||
HttpRequest request = new HttpRequest(method, httpRequest.url(), new HttpHeaders(httpRequest.headers()));
|
||||
if (!CoreUtils.isNullOrEmpty(httpRequest.body())) {
|
||||
request.setBody(httpRequest.body());
|
||||
}
|
||||
|
||||
return new HttpResponseWrapper(httpClient.execute(request));
|
||||
try (HttpResponse response = httpClient.sendSync(request, Context.NONE)) {
|
||||
com.microsoft.aad.msal4j.HttpResponse msalResponse = new com.microsoft.aad.msal4j.HttpResponse();
|
||||
msalResponse.statusCode(response.getStatusCode());
|
||||
msalResponse.body(response.getBodyAsBinaryData().toString());
|
||||
msalResponse.addHeaders(response.getHeaders().stream().collect(Collectors.toMap(HttpHeader::getName,
|
||||
HttpHeader::getValuesList)));
|
||||
return msalResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
package com.microsoft.azure.kusto.data.auth;
|
||||
|
||||
import com.azure.core.http.HttpHeader;
|
||||
import com.azure.core.http.HttpHeaders;
|
||||
import com.azure.core.http.HttpRequest;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.microsoft.aad.msal4j.IHttpResponse;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* This class wraps our azure HttpResponse, into both the asynchronous HttpResponse class and the synchronous IHttpResponse interface from the azure core
|
||||
* library.
|
||||
* This class completes the {@link HttpClientWrapper} class, which is responsible for wrapping the http client, this client wraps the response from the
|
||||
* client.
|
||||
*/
|
||||
public class HttpResponseWrapper extends HttpResponse implements IHttpResponse {
|
||||
org.apache.http.HttpResponse response;
|
||||
private byte[] body = null;
|
||||
|
||||
protected HttpResponseWrapper(HttpRequest request, org.apache.http.HttpResponse response) {
|
||||
super(request);
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
protected HttpResponseWrapper(org.apache.http.HttpResponse response) {
|
||||
this(null, response);
|
||||
}
|
||||
|
||||
// HttpResponse methods
|
||||
|
||||
@Override
|
||||
public int getStatusCode() {
|
||||
return response.getStatusLine().getStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeaderValue(String s) {
|
||||
Header firstHeader = response.getFirstHeader(s);
|
||||
return firstHeader != null ? firstHeader.getValue() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
Map<String, List<String>> newHeaders = new HashMap<>();
|
||||
Header[] allHeaders = response.getAllHeaders();
|
||||
|
||||
// This is required since one header can have multiple values.
|
||||
for (Header header : allHeaders) {
|
||||
if (newHeaders.containsKey(header.getName())) {
|
||||
newHeaders.get(header.getName()).add(header.getValue());
|
||||
} else {
|
||||
List<String> values = new ArrayList<>();
|
||||
values.add(header.getValue());
|
||||
newHeaders.put(header.getName(), values);
|
||||
}
|
||||
}
|
||||
return new HttpHeaders(newHeaders.entrySet().stream().map(e -> new HttpHeader(e.getKey(), e.getValue())).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ByteBuffer> getBody() {
|
||||
return getBodyAsByteArray().map(ByteBuffer::wrap).flux();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<byte[]> getBodyAsByteArray() {
|
||||
// We read the body only once, and then wrap it in a Mono.
|
||||
|
||||
if (body == null) {
|
||||
try {
|
||||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) {
|
||||
body = ArrayUtils.EMPTY_BYTE_ARRAY;
|
||||
} else {
|
||||
body = EntityUtils.toByteArray(response.getEntity());
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
body = new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
return Mono.just(body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getBodyAsString() {
|
||||
return getBodyAsByteArray().map(String::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getBodyAsString(Charset charset) {
|
||||
return getBodyAsByteArray().map(bytes -> new String(bytes, charset));
|
||||
}
|
||||
|
||||
// IHttpResponse methods
|
||||
|
||||
@Override
|
||||
public int statusCode() {
|
||||
return getStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> headers() {
|
||||
return getHeaders().stream().collect(Collectors.toMap(com.azure.core.util.Header::getName, com.azure.core.util.Header::getValuesList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String body() {
|
||||
String block = getBodyAsString().block();
|
||||
return block;
|
||||
}
|
||||
}
|
|
@ -2,13 +2,13 @@ package com.microsoft.azure.kusto.data.auth;
|
|||
|
||||
import com.azure.core.credential.AccessToken;
|
||||
import com.azure.core.credential.TokenRequestContext;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.identity.ManagedIdentityCredential;
|
||||
import com.azure.identity.ManagedIdentityCredentialBuilder;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
|||
|
||||
import com.microsoft.azure.kusto.data.instrumentation.MonitoredActivity;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
|||
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import com.microsoft.aad.msal4j.IConfidentialClientApplication;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.microsoft.azure.kusto.data.auth;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.SupplierTwoExceptions;
|
||||
import com.microsoft.azure.kusto.data.UriUtils;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
|
@ -10,7 +11,6 @@ import java.util.Map;
|
|||
|
||||
import com.microsoft.azure.kusto.data.instrumentation.MonitoredActivity;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.TraceableAttributes;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
|
|
@ -7,7 +7,7 @@ import com.microsoft.aad.msal4j.ClientCredentialFactory;
|
|||
import com.microsoft.aad.msal4j.IClientCertificate;
|
||||
import com.microsoft.aad.msal4j.IClientSecret;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import com.microsoft.aad.msal4j.PublicClientApplication;
|
|||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ public class KustoTrustedEndpoints {
|
|||
try {
|
||||
validateTrustedEndpoint(new URI(uri), loginEndpoint);
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new KustoClientInvalidConnectionStringException(ex);
|
||||
throw new KustoClientInvalidConnectionStringException(uri, ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,14 @@ package com.microsoft.azure.kusto.data.exceptions;
|
|||
This class represents an error that happened on the client side and is therefore considered permanent
|
||||
*/
|
||||
public class DataClientException extends KustoDataExceptionBase {
|
||||
public DataClientException(Exception ex) {
|
||||
this(ex.getMessage());
|
||||
}
|
||||
|
||||
public DataClientException(String message) {
|
||||
this(null, message);
|
||||
}
|
||||
|
||||
public DataClientException(String ingestionSource, String message) {
|
||||
this(ingestionSource, message, null);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
package com.microsoft.azure.kusto.data.exceptions;
|
||||
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microsoft.azure.kusto.data.Utils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.microsoft.azure.kusto.data.exceptions;
|
|||
/**
|
||||
* Raised when Kusto client is initialized with an invalid endpoint
|
||||
*/
|
||||
public class KustoClientInvalidConnectionStringException extends Exception {
|
||||
public class KustoClientInvalidConnectionStringException extends DataClientException {
|
||||
public KustoClientInvalidConnectionStringException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
@ -11,4 +11,8 @@ public class KustoClientInvalidConnectionStringException extends Exception {
|
|||
public KustoClientInvalidConnectionStringException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public KustoClientInvalidConnectionStringException(String clusterURL, String msg, Exception e) {
|
||||
super(clusterURL, msg, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.microsoft.azure.kusto.data.exceptions;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class WebException extends Exception {
|
||||
|
@ -23,6 +23,6 @@ public class WebException extends Exception {
|
|||
|
||||
@Nullable
|
||||
public Integer getStatusCode() {
|
||||
return httpResponse != null ? httpResponse.getStatusLine().getStatusCode() : null;
|
||||
return httpResponse != null ? httpResponse.getStatusCode() : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.microsoft.azure.kusto.data.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -16,10 +17,10 @@ import java.io.InputStream;
|
|||
*/
|
||||
public class CloseParentResourcesStream extends InputStream {
|
||||
private final InputStream innerStream;
|
||||
private final CloseableHttpResponse httpResponse;
|
||||
private final HttpResponse httpResponse;
|
||||
|
||||
public CloseParentResourcesStream(CloseableHttpResponse httpResponse) throws IOException {
|
||||
this.innerStream = httpResponse.getEntity().getContent();
|
||||
public CloseParentResourcesStream(HttpResponse httpResponse) throws IOException {
|
||||
this.innerStream = Utils.getResponseAsStream(httpResponse);
|
||||
this.httpResponse = httpResponse;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,108 +1,71 @@
|
|||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HeaderElementIterator;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.conn.ConnectionKeepAliveStrategy;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicHeaderElementIterator;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.core.http.HttpHeaderName;
|
||||
import com.azure.core.util.Header;
|
||||
import com.azure.core.util.HttpClientOptions;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A singleton factory of HTTP clients.
|
||||
* A static factory for HTTP clients.
|
||||
*/
|
||||
public class HttpClientFactory {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientFactory.class);
|
||||
|
||||
/**
|
||||
* Creates a new Apache HTTP client.
|
||||
* Creates a new HTTP client.
|
||||
*
|
||||
* @param providedProperties custom HTTP client properties
|
||||
* @return a new Apache HTTP client
|
||||
* @param properties custom HTTP client properties
|
||||
* @return a new HTTP client
|
||||
*/
|
||||
public static CloseableHttpClient create(HttpClientProperties providedProperties) {
|
||||
LOGGER.info("Creating new CloseableHttpClient client");
|
||||
final HttpClientProperties properties = Optional.ofNullable(providedProperties)
|
||||
.orElse(HttpClientProperties.builder().build());
|
||||
final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create()
|
||||
.useSystemProperties()
|
||||
.setMaxConnTotal(properties.maxConnectionTotal())
|
||||
.setMaxConnPerRoute(properties.maxConnectionRoute())
|
||||
.evictExpiredConnections()
|
||||
.evictIdleConnections(properties.maxIdleTime(), TimeUnit.SECONDS)
|
||||
.disableRedirectHandling();
|
||||
public static HttpClient create(HttpClientProperties properties) {
|
||||
LOGGER.info("Creating new HTTP Client");
|
||||
HttpClientOptions options = new HttpClientOptions();
|
||||
|
||||
if (properties.isKeepAlive()) {
|
||||
final ConnectionKeepAliveStrategy keepAliveStrategy = new CustomConnectionKeepAliveStrategy(properties.maxKeepAliveTime());
|
||||
httpClientBuilder.setKeepAliveStrategy(keepAliveStrategy);
|
||||
// If all properties are null, create with default client options
|
||||
if (properties == null) {
|
||||
options.setResponseTimeout(Duration.ofMinutes(10));
|
||||
return HttpClient.createDefault(options);
|
||||
}
|
||||
|
||||
if (properties.getPlanner() != null) {
|
||||
httpClientBuilder.setRoutePlanner(properties.getPlanner());
|
||||
// MS Docs indicate that all setters handle nulls so even if these values are null everything should "just work"
|
||||
// Note that the first discovered HttpClientProvider class is loaded. HttpClientProviders can be swapped in or out
|
||||
// by simply
|
||||
// Docs: https://learn.microsoft.com/en-us/java/api/com.azure.core.util.httpclientoptions?view=azure-java-stable
|
||||
|
||||
options.setMaximumConnectionPoolSize(properties.maxConnectionTotal());
|
||||
options.setConnectionIdleTimeout(Duration.ofSeconds(properties.maxIdleTime()));
|
||||
options.setResponseTimeout(properties.timeout());
|
||||
options.setHttpClientProvider(properties.provider());
|
||||
|
||||
// Set Keep-Alive headers if they were requested.
|
||||
// NOTE: Servers are not obligated to honor client requested Keep-Alive values
|
||||
if (properties.isKeepAlive()) {
|
||||
Header keepAlive = new Header(HttpHeaderName.CONNECTION.getCaseSensitiveName(), "Keep-Alive");
|
||||
// Keep-Alive is Non-standard from the client so core does not have an enum for it
|
||||
Header keepAliveTimeout = new Header("Keep-Alive", "timeout=" + properties.maxKeepAliveTime());
|
||||
|
||||
List<Header> headers = new ArrayList<>();
|
||||
headers.add(keepAlive);
|
||||
headers.add(keepAliveTimeout);
|
||||
|
||||
options.setHeaders(headers);
|
||||
}
|
||||
|
||||
if (properties.getProxy() != null) {
|
||||
httpClientBuilder.setProxy(properties.getProxy());
|
||||
options.setProxyOptions(properties.getProxy());
|
||||
}
|
||||
|
||||
if (properties.supportedProtocols() != null) {
|
||||
// Todo: Is the per route connection maximum needed anymore?
|
||||
|
||||
try {
|
||||
httpClientBuilder.setSSLSocketFactory(new SSLConnectionSocketFactory(SSLContext.getDefault(), properties.supportedProtocols(), null,
|
||||
SSLConnectionSocketFactory.getDefaultHostnameVerifier()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOGGER.error("Failed to set supported protocols", e);
|
||||
}
|
||||
}
|
||||
|
||||
return httpClientBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom connection keep-alive strategy that uses the server instructions set in the {@code Keep-Alive}
|
||||
* response header; if the response doesn't contain a {@code Keep-Alive} header, the client will use a configurable
|
||||
* keep-alive period.
|
||||
*/
|
||||
static class CustomConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {
|
||||
|
||||
private final int defaultKeepAlive;
|
||||
|
||||
/**
|
||||
* The default keep-alive time.
|
||||
*
|
||||
* @param defaultKeepAlive the keep-alive time expressed in seconds
|
||||
*/
|
||||
CustomConnectionKeepAliveStrategy(int defaultKeepAlive) {
|
||||
this.defaultKeepAlive = defaultKeepAlive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getKeepAliveDuration(HttpResponse httpResponse, HttpContext httpContext) {
|
||||
// honor 'keep-alive' header
|
||||
HeaderElementIterator it = new BasicHeaderElementIterator(httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE));
|
||||
while (it.hasNext()) {
|
||||
HeaderElement he = it.nextElement();
|
||||
String param = he.getName();
|
||||
String value = he.getValue();
|
||||
if (value != null && param.equalsIgnoreCase("timeout")) {
|
||||
return Long.parseLong(value) * 1000;
|
||||
}
|
||||
}
|
||||
// otherwise keep alive for default seconds
|
||||
return defaultKeepAlive * 1000L;
|
||||
}
|
||||
return HttpClient.createDefault(options);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package com.microsoft.azure.kusto.data;
|
||||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.conn.routing.HttpRoutePlanner;
|
||||
import com.azure.core.http.HttpClientProvider;
|
||||
import com.azure.core.http.ProxyOptions;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* HTTP client properties.
|
||||
* TODO: move to http package on next major
|
||||
*/
|
||||
public class HttpClientProperties {
|
||||
private final Integer maxIdleTime;
|
||||
|
@ -13,9 +14,9 @@ public class HttpClientProperties {
|
|||
private final Integer maxKeepAliveTime;
|
||||
private final Integer maxConnectionTotal;
|
||||
private final Integer maxConnectionRoute;
|
||||
private final HttpHost proxy;
|
||||
private final HttpRoutePlanner routePlanner;
|
||||
private final String[] supportedProtocols;
|
||||
private final Duration timeout;
|
||||
private final Class<? extends HttpClientProvider> provider;
|
||||
private final ProxyOptions proxy;
|
||||
|
||||
private HttpClientProperties(HttpClientPropertiesBuilder builder) {
|
||||
this.maxIdleTime = builder.maxIdleTime;
|
||||
|
@ -23,9 +24,9 @@ public class HttpClientProperties {
|
|||
this.maxKeepAliveTime = builder.maxKeepAliveTime;
|
||||
this.maxConnectionTotal = builder.maxConnectionsTotal;
|
||||
this.maxConnectionRoute = builder.maxConnectionsPerRoute;
|
||||
this.timeout = builder.timeout;
|
||||
this.provider = builder.provider;
|
||||
this.proxy = builder.proxy;
|
||||
this.routePlanner = builder.routePlanner;
|
||||
this.supportedProtocols = builder.supportedProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,7 +49,7 @@ public class HttpClientProperties {
|
|||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a custom connection keep-alive time should be used. If set to {@code false}, the HTTP
|
||||
* Indicates whether or not a custom connection keep-alive time should be used. If set to {@code false}, the HTTP
|
||||
* client will use the default connection keep-alive strategy, which is to use only the server instructions
|
||||
* (if any) set in the {@code Keep-Alive} response header.
|
||||
* If set to {@code true}, the HTTP client will use a custom connection keep-alive strategy which uses the
|
||||
|
@ -94,23 +95,33 @@ public class HttpClientProperties {
|
|||
return maxConnectionRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default response timeout to apply to HTTP requests
|
||||
*
|
||||
* @return the timeout
|
||||
*/
|
||||
public Duration timeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTTP Client Provider used by Azure Core when constructing HTTP Client instances.
|
||||
*
|
||||
* @return the provider
|
||||
*/
|
||||
public Class<? extends HttpClientProvider> provider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* The proxy to use when connecting to the remote server.
|
||||
*
|
||||
* @return the proxy
|
||||
*/
|
||||
public HttpHost getProxy() {
|
||||
public ProxyOptions getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public HttpRoutePlanner getPlanner() {
|
||||
return routePlanner;
|
||||
}
|
||||
|
||||
public String[] supportedProtocols() {
|
||||
return supportedProtocols;
|
||||
}
|
||||
|
||||
public static class HttpClientPropertiesBuilder {
|
||||
|
||||
private Integer maxIdleTime = 120;
|
||||
|
@ -118,9 +129,9 @@ public class HttpClientProperties {
|
|||
private Integer maxKeepAliveTime = 120;
|
||||
private Integer maxConnectionsTotal = 40;
|
||||
private Integer maxConnectionsPerRoute = 40;
|
||||
private HttpHost proxy = null;
|
||||
private HttpRoutePlanner routePlanner = null;
|
||||
private String[] supportedProtocols = null;
|
||||
private Duration timeout = Duration.ofMinutes(10);
|
||||
private Class<? extends HttpClientProvider> provider = null;
|
||||
private ProxyOptions proxy = null;
|
||||
|
||||
private HttpClientPropertiesBuilder() {
|
||||
}
|
||||
|
@ -139,7 +150,7 @@ public class HttpClientProperties {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set whether a custom connection keep-alive time should be used. If set to {@code false}, the HTTP
|
||||
* Set whether or not a custom connection keep-alive time should be used. If set to {@code false}, the HTTP
|
||||
* client will use the default connection keep-alive strategy, which is to use only the server instructions
|
||||
* (if any) set in the {@code Keep-Alive} response header.
|
||||
* If set to {@code true}, the HTTP client will use a custom connection keep-alive strategy which uses the
|
||||
|
@ -194,41 +205,42 @@ public class HttpClientProperties {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP Client Provider used by Azure Core when constructing HTTP Client instances.
|
||||
*
|
||||
* @param provider the requested HTTP Client provider class
|
||||
* @return the builder instance
|
||||
*/
|
||||
public HttpClientPropertiesBuilder provider(Class<? extends HttpClientProvider> provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a response timeout to use by default on the client's requests.
|
||||
*
|
||||
* @param timeout the requested response timeout
|
||||
* @return the builder instance
|
||||
*/
|
||||
public HttpClientPropertiesBuilder timeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a proxy server to use for the client.
|
||||
*
|
||||
* @param proxy the proxy server
|
||||
* @return the builder instance
|
||||
*/
|
||||
public HttpClientPropertiesBuilder proxy(HttpHost proxy) {
|
||||
public HttpClientPropertiesBuilder proxy(ProxyOptions proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the {@link #proxy} parameter, and can be used to create more complex proxies.
|
||||
*
|
||||
* @param routePlanner the custom route planner
|
||||
* @return the builder instance
|
||||
*/
|
||||
public HttpClientPropertiesBuilder routePlanner(HttpRoutePlanner routePlanner) {
|
||||
this.routePlanner = routePlanner;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of supported SSL/TLS protocols.
|
||||
* @param tlsProtocols the list of supported protocols
|
||||
* @return the builder instance
|
||||
*/
|
||||
public HttpClientPropertiesBuilder supportedProtocols(String[] tlsProtocols) {
|
||||
this.supportedProtocols = tlsProtocols;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientProperties build() {
|
||||
return new HttpClientProperties(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,259 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.microsoft.azure.kusto.data.Utils;
|
||||
import com.microsoft.azure.kusto.data.auth.CloudInfo;
|
||||
import com.microsoft.azure.kusto.data.exceptions.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.conn.EofSensorInputStream;
|
||||
import org.apache.http.entity.AbstractHttpEntity;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.DeflaterInputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class HttpPostUtils {
|
||||
private static final int MAX_REDIRECT_COUNT = 1;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
private HttpPostUtils() {
|
||||
// Hide constructor, as this is a static utility class
|
||||
}
|
||||
|
||||
public static String post(CloseableHttpClient httpClient, String urlStr, AbstractHttpEntity requestEntity, long timeoutMs,
|
||||
Map<String, String> headers)
|
||||
throws DataServiceException, DataClientException {
|
||||
URI url = parseUriFromUrlString(urlStr);
|
||||
|
||||
try {
|
||||
HttpPost request = setupHttpPostRequest(url, requestEntity, headers);
|
||||
int requestTimeout = timeoutMs > Integer.MAX_VALUE ? Integer.MAX_VALUE : Math.toIntExact(timeoutMs);
|
||||
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(requestTimeout).build();
|
||||
request.setConfig(requestConfig);
|
||||
|
||||
// Execute and get the response
|
||||
try (CloseableHttpResponse response = httpClient.execute(request)) {
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
if (entity != null) {
|
||||
StatusLine statusLine = response.getStatusLine();
|
||||
String responseContent = EntityUtils.toString(entity);
|
||||
switch (statusLine.getStatusCode()) {
|
||||
case HttpStatus.SC_OK:
|
||||
return responseContent;
|
||||
case HttpStatus.SC_TOO_MANY_REQUESTS:
|
||||
throw new ThrottleException(urlStr);
|
||||
default:
|
||||
throw createExceptionFromResponse(urlStr, response, null, responseContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DataServiceException(urlStr, "IOException in post request:" + e.getMessage(), !Utils.isRetriableIOException(e));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static InputStream postToStreamingOutput(CloseableHttpClient httpClient, String url, String payload, long timeoutMs, Map<String, String> headers)
|
||||
throws DataServiceException, DataClientException {
|
||||
return postToStreamingOutput(httpClient, url, payload, timeoutMs, headers, 0);
|
||||
}
|
||||
|
||||
public static InputStream postToStreamingOutput(CloseableHttpClient httpClient, String url, String payload, long timeoutMs, Map<String, String> headers,
|
||||
int redirectCount)
|
||||
throws DataServiceException, DataClientException {
|
||||
long timeoutTimeMs = System.currentTimeMillis() + timeoutMs;
|
||||
URI uri = parseUriFromUrlString(url);
|
||||
|
||||
boolean returnInputStream = false;
|
||||
String errorFromResponse = null;
|
||||
/*
|
||||
* The caller must close the inputStream to close the following underlying resources (httpClient and httpResponse). We use CloseParentResourcesStream so
|
||||
* that when the stream is closed, these resources are closed as well. We shouldn't need to do that, per
|
||||
* https://hc.apache.org/httpcomponents-client-4.5.x/current/tutorial/html/fundamentals.html: "In order to ensure proper release of system resources one
|
||||
* must close either the content stream associated with the entity or the response itself." However, in my testing this wasn't reliably the case. We
|
||||
* further use EofSensorInputStream to close the stream even if not explicitly closed, once all content is consumed.
|
||||
*/
|
||||
CloseableHttpResponse httpResponse = null;
|
||||
try {
|
||||
StringEntity requestEntity = new StringEntity(payload, ContentType.APPLICATION_JSON);
|
||||
HttpPost httpPost = setupHttpPostRequest(uri, requestEntity, headers);
|
||||
int requestTimeout = Math.toIntExact(timeoutTimeMs - System.currentTimeMillis());
|
||||
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(requestTimeout).build();
|
||||
httpPost.setConfig(requestConfig);
|
||||
|
||||
httpResponse = httpClient.execute(httpPost);
|
||||
|
||||
int responseStatusCode = httpResponse.getStatusLine().getStatusCode();
|
||||
|
||||
if (responseStatusCode == HttpStatus.SC_OK) {
|
||||
InputStream contentStream = new EofSensorInputStream(new CloseParentResourcesStream(httpResponse), null);
|
||||
Optional<Header> contentEncoding = Arrays.stream(httpResponse.getHeaders(HttpHeaders.CONTENT_ENCODING)).findFirst();
|
||||
if (contentEncoding.isPresent()) {
|
||||
if (contentEncoding.get().getValue().contains("gzip")) {
|
||||
GZIPInputStream gzipInputStream = new GZIPInputStream(contentStream);
|
||||
returnInputStream = true;
|
||||
return gzipInputStream;
|
||||
} else if (contentEncoding.get().getValue().contains("deflate")) {
|
||||
DeflaterInputStream deflaterInputStream = new DeflaterInputStream(contentStream);
|
||||
returnInputStream = true;
|
||||
return deflaterInputStream;
|
||||
}
|
||||
}
|
||||
// Though the server responds with a gzip/deflate Content-Encoding header, we reach here because httpclient uses LazyDecompressingStream which
|
||||
// handles the above logic
|
||||
returnInputStream = true;
|
||||
return contentStream;
|
||||
}
|
||||
|
||||
errorFromResponse = EntityUtils.toString(httpResponse.getEntity());
|
||||
// Ideal to close here (as opposed to finally) so that if any data can't be flushed, the exception will be properly thrown and handled
|
||||
httpResponse.close();
|
||||
|
||||
if (shouldPostToOriginalUrlDueToRedirect(redirectCount, responseStatusCode)) {
|
||||
Optional<Header> redirectLocation = Arrays.stream(httpResponse.getHeaders(HttpHeaders.LOCATION)).findFirst();
|
||||
if (redirectLocation.isPresent() && !redirectLocation.get().getValue().equals(url)) {
|
||||
return postToStreamingOutput(httpClient, redirectLocation.get().getValue(), payload, timeoutMs, headers, redirectCount + 1);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new DataServiceException(url, "postToStreamingOutput failed to get or decompress response stream",
|
||||
ex, false);
|
||||
} catch (Exception ex) {
|
||||
throw createExceptionFromResponse(url, httpResponse, ex, errorFromResponse);
|
||||
} finally {
|
||||
closeResourcesIfNeeded(returnInputStream, httpResponse);
|
||||
}
|
||||
|
||||
throw createExceptionFromResponse(url, httpResponse, null, errorFromResponse);
|
||||
}
|
||||
|
||||
public static DataServiceException createExceptionFromResponse(String url, HttpResponse httpResponse, Exception thrownException, String errorFromResponse) {
|
||||
if (httpResponse == null) {
|
||||
return new DataServiceException(url, "POST failed to send request", thrownException, false);
|
||||
} else {
|
||||
/*
|
||||
* TODO: When we add another streaming API that returns a KustoOperationResult, we'll need to handle the 2 types of content errors this API can
|
||||
* return: (1) Inline error (engine identifies error after it starts building the json result), or (2) in the KustoOperationResult's
|
||||
* QueryCompletionInformation, both of which present with "200 OK". See .Net's DataReaderParser.
|
||||
*/
|
||||
String activityId = determineActivityId(httpResponse);
|
||||
String message = errorFromResponse;
|
||||
WebException formattedException = new WebException(errorFromResponse, httpResponse, thrownException);
|
||||
boolean isPermanent = false;
|
||||
if (!StringUtils.isBlank(errorFromResponse)) {
|
||||
try {
|
||||
JsonNode jsonObject = Utils.getObjectMapper().readTree(errorFromResponse);
|
||||
if (jsonObject.has("error")) {
|
||||
formattedException = new DataWebException(errorFromResponse, httpResponse, thrownException);
|
||||
OneApiError apiError = ((DataWebException) formattedException).getApiError();
|
||||
message = apiError.getDescription();
|
||||
isPermanent = apiError.isPermanent();
|
||||
} else if (jsonObject.has("message")) {
|
||||
message = jsonObject.get("message").asText();
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
// It's not ideal to use an exception here for control flow, but we can't know if it's a valid JSON until we try to parse it
|
||||
LOGGER.debug("json processing error happened while parsing errorFromResponse {}" + e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
message = String.format("Http StatusCode='%s'", httpResponse.getStatusLine().toString());
|
||||
}
|
||||
|
||||
return new DataServiceException(
|
||||
url,
|
||||
String.format("%s, ActivityId='%s'", message, activityId),
|
||||
formattedException,
|
||||
isPermanent);
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeResourcesIfNeeded(boolean returnInputStream, CloseableHttpResponse httpResponse) {
|
||||
// If we close the resources after returning the InputStream to the caller, they won't be able to read from it
|
||||
if (!returnInputStream) {
|
||||
try {
|
||||
if (httpResponse != null) {
|
||||
httpResponse.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// There's nothing we can do if the resources can't be closed, and we don't want to add an exception to the signature
|
||||
LOGGER.error("Couldn't close HttpResponse. This won't affect the POST call, but should be investigated.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldPostToOriginalUrlDueToRedirect(int redirectCount, int status) {
|
||||
return (status == HttpStatus.SC_MOVED_TEMPORARILY || status == HttpStatus.SC_TEMPORARY_REDIRECT)
|
||||
&& redirectCount + 1 <= MAX_REDIRECT_COUNT;
|
||||
}
|
||||
|
||||
private static String determineActivityId(HttpResponse httpResponse) {
|
||||
String activityId = "";
|
||||
Optional<Header> activityIdHeader = Arrays.stream(httpResponse.getHeaders("x-ms-activity-id")).findFirst();
|
||||
if (activityIdHeader.isPresent()) {
|
||||
activityId = activityIdHeader.get().getValue();
|
||||
}
|
||||
return activityId;
|
||||
}
|
||||
|
||||
private static HttpPost setupHttpPostRequest(URI uri, AbstractHttpEntity requestEntity, Map<String, String> headers) {
|
||||
HttpPost request = new HttpPost(uri);
|
||||
|
||||
// Request parameters and other properties
|
||||
request.setEntity(requestEntity);
|
||||
|
||||
request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip,deflate");
|
||||
request.addHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
|
||||
// TODO - maybe save this in a resouce
|
||||
String KUSTO_API_VERSION = "2019-02-13";
|
||||
request.addHeader("x-ms-version", KUSTO_API_VERSION);
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
request.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static URI parseUriFromUrlString(String url) throws DataClientException {
|
||||
try {
|
||||
URL cleanUrl = new URL(url);
|
||||
if ("https".equalsIgnoreCase(cleanUrl.getProtocol()) || url.toLowerCase().startsWith(CloudInfo.LOCALHOST)) {
|
||||
return new URI(cleanUrl.getProtocol(), cleanUrl.getUserInfo(), cleanUrl.getHost(), cleanUrl.getPort(), cleanUrl.getPath(), cleanUrl.getQuery(),
|
||||
cleanUrl.getRef());
|
||||
} else {
|
||||
throw new DataClientException(url, "Cannot forward security token to a remote service over insecure " +
|
||||
"channel (http://)");
|
||||
}
|
||||
} catch (URISyntaxException | MalformedURLException e) {
|
||||
throw new DataClientException(url, "Error parsing target URL in post request:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import com.azure.core.http.HttpHeaderName;
|
||||
import com.azure.core.http.HttpMethod;
|
||||
import com.azure.core.http.HttpRequest;
|
||||
import com.azure.core.util.BinaryData;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.microsoft.azure.kusto.data.Utils;
|
||||
import com.microsoft.azure.kusto.data.auth.CloudInfo;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.req.KustoRequest;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class HttpRequestBuilder {
|
||||
|
||||
// TODO - maybe save this in a resource
|
||||
private static final String KUSTO_API_VERSION = "2019-02-13";
|
||||
private static final String JAVA_INGEST_ACTIVITY_TYPE_PREFIX = "DN.JavaClient.Execute";
|
||||
private static final String CLIENT_VERSION_HEADER = "x-ms-client-version";
|
||||
private static final String APP_HEADER = "x-ms-app";
|
||||
private static final String USER_HEADER = "x-ms-user";
|
||||
|
||||
private final HttpRequest request;
|
||||
|
||||
public static HttpRequestBuilder fromExistingRequest(HttpRequest request) {
|
||||
return new HttpRequestBuilder(request);
|
||||
}
|
||||
|
||||
public static HttpRequestBuilder newPost(String url) throws DataClientException {
|
||||
return new HttpRequestBuilder(HttpMethod.POST, url);
|
||||
}
|
||||
|
||||
private HttpRequestBuilder(HttpRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder(HttpMethod method, String url) throws DataClientException {
|
||||
URL cleanURL = parseURLString(url);
|
||||
request = new HttpRequest(method, cleanURL);
|
||||
}
|
||||
|
||||
public HttpRequestBuilder createCommandPayload(KustoRequest kr) {
|
||||
ObjectNode json = Utils.getObjectMapper().createObjectNode()
|
||||
.put("db", kr.getDatabase())
|
||||
.put("csl", kr.getCommand());
|
||||
|
||||
if (kr.getProperties() != null) {
|
||||
json.put("properties", kr.getProperties().toString());
|
||||
}
|
||||
|
||||
request.setBody(json.toString());
|
||||
|
||||
// When executing a query/command, we always add content type
|
||||
// Updated to remove Fed True from command headers per PR #342
|
||||
request.setHeader(HttpHeaderName.CONTENT_TYPE, "application/json; charset=utf-8");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder withBody(BinaryData body) {
|
||||
request.setBody(body);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder withAuthorization(String value) {
|
||||
if (value != null) {
|
||||
request.setHeader(HttpHeaderName.AUTHORIZATION, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder withContentEncoding(String contentEncoding) {
|
||||
if (contentEncoding != null) {
|
||||
request.setHeader(HttpHeaderName.CONTENT_ENCODING, contentEncoding);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder withContentType(String contentType) {
|
||||
if (contentType != null) {
|
||||
request.setHeader(HttpHeaderName.CONTENT_TYPE, contentType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder withHeaders(Map<String, String> headers) {
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
request.setHeader(HttpHeaderName.fromString(entry.getKey()), entry.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequestBuilder withTracing(HttpTracing tracing) {
|
||||
return this.withHeaders(getTracingHeaders(tracing));
|
||||
}
|
||||
|
||||
public HttpRequestBuilder withURL(String url) throws DataClientException {
|
||||
URL cleanURL = parseURLString(url);
|
||||
request.setUrl(cleanURL);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpRequest build() {
|
||||
|
||||
// Set global headers that get added to all requests
|
||||
request.setHeader(HttpHeaderName.ACCEPT_ENCODING, "gzip,deflate");
|
||||
request.setHeader(HttpHeaderName.ACCEPT, "application/json");
|
||||
// Removed content type from this method because the request should already have a type that is not always json
|
||||
|
||||
request.setHeader(HttpHeaderName.fromString("x-ms-version"), KUSTO_API_VERSION);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static URL parseURLString(String url) throws DataClientException {
|
||||
try {
|
||||
// By nature of the try/catch only valid URLs pass through this function
|
||||
URL cleanUrl = new URL(url);
|
||||
// Further checking here to ensure the URL uses HTTPS if not pointed at localhost
|
||||
if ("https".equalsIgnoreCase(cleanUrl.getProtocol()) || url.toLowerCase().startsWith(CloudInfo.LOCALHOST)) {
|
||||
return cleanUrl;
|
||||
} else {
|
||||
throw new DataClientException(url, "Cannot forward security token to a remote service over insecure " +
|
||||
"channel (http://)");
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
throw new DataClientException(url, "Error parsing target URL in post request:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getTracingHeaders(HttpTracing tracing) {
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
String version = tracing.getClientDetails().getClientVersionForTracing();
|
||||
if (StringUtils.isNotBlank(version)) {
|
||||
headers.put(CLIENT_VERSION_HEADER, version);
|
||||
}
|
||||
|
||||
String app = (tracing.getProperties() == null || tracing.getProperties().getApplication() == null)
|
||||
? tracing.getClientDetails().getApplicationForTracing()
|
||||
: tracing.getProperties().getApplication();
|
||||
if (StringUtils.isNotBlank(app)) {
|
||||
headers.put(APP_HEADER, app);
|
||||
}
|
||||
|
||||
String user = (tracing.getProperties() == null || tracing.getProperties().getUser() == null) ? tracing.getClientDetails().getUserNameForTracing()
|
||||
: tracing.getProperties().getUser();
|
||||
if (StringUtils.isNotBlank(user)) {
|
||||
headers.put(USER_HEADER, user);
|
||||
}
|
||||
|
||||
String clientRequestId;
|
||||
if (tracing.getProperties() != null && StringUtils.isNotBlank(tracing.getProperties().getClientRequestId())) {
|
||||
clientRequestId = tracing.getProperties().getClientRequestId();
|
||||
} else {
|
||||
clientRequestId = String.format("%s;%s", tracing.getClientRequestIdPrefix(), UUID.randomUUID());
|
||||
}
|
||||
|
||||
headers.put("x-ms-client-request-id", clientRequestId);
|
||||
|
||||
// Configures Keep-Alive on all requests traced
|
||||
headers.put("Connection", "Keep-Alive");
|
||||
|
||||
UUID activityId = UUID.randomUUID();
|
||||
String activityContext = String.format("%s%s/%s, ActivityId=%s, ParentId=%s, ClientRequestId=%s",
|
||||
JAVA_INGEST_ACTIVITY_TYPE_PREFIX, tracing.getActivityTypeSuffix(), activityId, activityId, activityId, clientRequestId);
|
||||
headers.put("x-ms-activitycontext", activityContext);
|
||||
|
||||
// replace non-ascii characters in header values with '?'
|
||||
headers.replaceAll((_i, v) -> v == null ? null : v.replaceAll("[^\\x00-\\x7F]", "?"));
|
||||
return headers;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
public class HttpStatus {
|
||||
public static final int OK = 200;
|
||||
public static final int FOUND = 302;
|
||||
public static final int TEMP_REDIRECT = 207;
|
||||
public static final int UNAUTHORIZED = 401;
|
||||
public static final int FORBIDDEN = 403;
|
||||
public static final int NOT_FOUND = 404;
|
||||
public static final int TOO_MANY_REQS = 429;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import com.microsoft.azure.kusto.data.ClientDetails;
|
||||
import com.microsoft.azure.kusto.data.ClientRequestProperties;
|
||||
|
||||
public class HttpTracing {
|
||||
private ClientRequestProperties properties;
|
||||
private String clientRequestIdPrefix;
|
||||
private String activityTypeSuffix;
|
||||
private ClientDetails clientDetails;
|
||||
|
||||
private HttpTracing() {
|
||||
}
|
||||
|
||||
public ClientRequestProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(ClientRequestProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public String getClientRequestIdPrefix() {
|
||||
return clientRequestIdPrefix;
|
||||
}
|
||||
|
||||
public void setClientRequestIdPrefix(String clientRequestIdPrefix) {
|
||||
this.clientRequestIdPrefix = clientRequestIdPrefix;
|
||||
}
|
||||
|
||||
public String getActivityTypeSuffix() {
|
||||
return activityTypeSuffix;
|
||||
}
|
||||
|
||||
public void setActivityTypeSuffix(String activityTypeSuffix) {
|
||||
this.activityTypeSuffix = activityTypeSuffix;
|
||||
}
|
||||
|
||||
public ClientDetails getClientDetails() {
|
||||
return clientDetails;
|
||||
}
|
||||
|
||||
public void setClientDetails(ClientDetails clientDetails) {
|
||||
this.clientDetails = clientDetails;
|
||||
}
|
||||
|
||||
public static HttpTracingBuilder newBuilder() {
|
||||
return new HttpTracingBuilder();
|
||||
}
|
||||
|
||||
public static class HttpTracingBuilder {
|
||||
|
||||
private final HttpTracing tracing = new HttpTracing();
|
||||
|
||||
public HttpTracingBuilder() {
|
||||
}
|
||||
|
||||
public HttpTracingBuilder withClientDetails(ClientDetails clientDetails) {
|
||||
tracing.setClientDetails(clientDetails);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpTracingBuilder withProperties(ClientRequestProperties properties) {
|
||||
tracing.setProperties(properties);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpTracingBuilder withRequestPrefix(String requestPrefix) {
|
||||
tracing.setClientRequestIdPrefix(requestPrefix);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpTracingBuilder withActivitySuffix(String activitySuffix) {
|
||||
tracing.setActivityTypeSuffix(activitySuffix);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpTracing build() {
|
||||
return tracing;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.microsoft.azure.kusto.data.instrumentation;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class MonitoredActivity {
|
||||
public static void invoke(Runnable runnable, String nameOfSpan) {
|
||||
|
@ -14,6 +15,16 @@ public class MonitoredActivity {
|
|||
}
|
||||
}
|
||||
|
||||
public static <T> T invoke(SupplierNoException<T> supplier, String nameOfSpan) {
|
||||
return invoke(supplier, nameOfSpan, new HashMap<>());
|
||||
}
|
||||
|
||||
public static <T> T invoke(SupplierNoException<T> supplier, String nameOfSpan, Map<String, String> attributes) {
|
||||
try (Tracer.Span span = Tracer.startSpan(nameOfSpan, attributes)) {
|
||||
return supplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, U extends Exception> T invoke(SupplierOneException<T, U> supplier, String nameOfSpan) throws U {
|
||||
return invoke((SupplierTwoExceptions<T, U, U>) supplier::get, nameOfSpan, new HashMap<>());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.microsoft.azure.kusto.data.instrumentation;
|
||||
|
||||
/**
|
||||
* Supplier is a functional interface that can be used to execute data operations.
|
||||
* @param <T>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SupplierNoException<T> {
|
||||
|
||||
/**
|
||||
* Execute a data operation.
|
||||
* @return T
|
||||
*/
|
||||
T get();
|
||||
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package com.microsoft.azure.kusto.data.req;
|
||||
|
||||
import com.microsoft.azure.kusto.data.ClientRequestProperties;
|
||||
import com.microsoft.azure.kusto.data.CommandType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class KustoRequest {
|
||||
|
||||
private static final String ADMIN_COMMANDS_PREFIX = ".";
|
||||
private static final String DEFAULT_DATABASE_NAME = "NetDefaultDb";
|
||||
|
||||
private String command;
|
||||
private CommandType commandType;
|
||||
private String database;
|
||||
private ClientRequestProperties properties;
|
||||
|
||||
/**
|
||||
* A constructor providing only the command to be executed.
|
||||
* @param command the command to be executed
|
||||
*/
|
||||
public KustoRequest(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor providing the command to be executed, and the command type.
|
||||
* @param command the command to be executed
|
||||
* @param commandType the command type
|
||||
*/
|
||||
public KustoRequest(String command, CommandType commandType) {
|
||||
this.command = command;
|
||||
this.commandType = commandType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor providing the command to be executed and the database to target.
|
||||
* @param command the command to be executed
|
||||
* @param database the name of the database to target
|
||||
*/
|
||||
public KustoRequest(String command, String database) {
|
||||
this.command = command;
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor providing the command to be executed, the database to target, and the command type.
|
||||
* @param command the command to be executed
|
||||
* @param database the name of the database to target
|
||||
* @param commandType the command type
|
||||
*/
|
||||
public KustoRequest(String command, String database, CommandType commandType) {
|
||||
this.command = command;
|
||||
this.database = database;
|
||||
this.commandType = commandType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor providing the command to be executed and sanitized query parameters.
|
||||
* @param command the command to be executed
|
||||
* @param properties a map of query parameters and other request properties
|
||||
*/
|
||||
public KustoRequest(String command, ClientRequestProperties properties) {
|
||||
this.command = command;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor providing the command to be executed, the database to target, and the command type.
|
||||
* @param command the command to be executed
|
||||
* @param properties a map of query parameters and other request properties
|
||||
* @param commandType the command type
|
||||
*/
|
||||
public KustoRequest(String command, ClientRequestProperties properties, CommandType commandType) {
|
||||
this.command = command;
|
||||
this.properties = properties;
|
||||
this.commandType = commandType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor providing the command to be executed, sanitized query parameters, and the database to target.
|
||||
* @param command the command to be executed
|
||||
* @param database the name of the database to target
|
||||
* @param properties a map of query parameters and other request properties
|
||||
*/
|
||||
public KustoRequest(String command, String database, ClientRequestProperties properties) {
|
||||
this.command = command;
|
||||
this.database = database;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* An all args constructor providing the command to be executed, sanitized query parameters, the database to target, and the command type.
|
||||
* @param command the command to be executed
|
||||
* @param database the name of the database to target
|
||||
* @param properties a map of query parameters and other request properties
|
||||
* @param commandType the command type
|
||||
*/
|
||||
public KustoRequest(String command, String database, ClientRequestProperties properties, CommandType commandType) {
|
||||
this.command = command;
|
||||
this.database = database;
|
||||
this.properties = properties;
|
||||
this.commandType = commandType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for this KustoRequest object's inner command String.
|
||||
* @return the command
|
||||
*/
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for this KustoRequest object's inner command String.
|
||||
* @param command the command
|
||||
*/
|
||||
public void setCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for this KustoRequest object's inner command type.
|
||||
* @return the command type
|
||||
*/
|
||||
public CommandType getCommandType() {
|
||||
return commandType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for this KustoRequest object's inner command type.
|
||||
* @param commandType the command type
|
||||
*/
|
||||
public void setCommandType(CommandType commandType) {
|
||||
this.commandType = commandType;
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for this KustoRequest object's inner database String.
|
||||
* @return the database name
|
||||
*/
|
||||
public String getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for this KustoRequest object's inner database String.
|
||||
* @param database the database name
|
||||
*/
|
||||
public void setDatabase(String database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for this KustoRequest object's inner ClientRequestProperties.
|
||||
* @return the properties
|
||||
*/
|
||||
public ClientRequestProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for this KustoRequest object's inner ClientRequestProperties.
|
||||
* @param properties the properties
|
||||
*/
|
||||
public void setProperties(ClientRequestProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/** Validates and optimizes the KustoQuery object. */
|
||||
public void validateAndOptimize() {
|
||||
if (database == null) {
|
||||
database = DEFAULT_DATABASE_NAME;
|
||||
}
|
||||
// Argument validation
|
||||
if (StringUtils.isEmpty(database)) {
|
||||
throw new IllegalArgumentException("Database is empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(command)) {
|
||||
throw new IllegalArgumentException("Command is empty");
|
||||
}
|
||||
// Optimize the command by removing superfluous whitespace
|
||||
command = command.trim();
|
||||
// Set command type if it wasn't provided. This is solely used by the deprecated methods in Client interface
|
||||
// and the executeToJSON methods since they bypass the query/mgmt methods.
|
||||
if (commandType == null) {
|
||||
commandType = determineCommandType(command);
|
||||
}
|
||||
}
|
||||
|
||||
private CommandType determineCommandType(String command) {
|
||||
if (command.startsWith(ADMIN_COMMANDS_PREFIX)) {
|
||||
return CommandType.ADMIN_COMMAND;
|
||||
}
|
||||
return CommandType.QUERY;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.microsoft.azure.kusto.data.req;
|
||||
|
||||
import com.azure.core.http.HttpRequest;
|
||||
|
||||
public class KustoRequestContext {
|
||||
private KustoRequest sdkRequest;
|
||||
private HttpRequest httpRequest;
|
||||
|
||||
public KustoRequestContext(KustoRequest sdkRequest, HttpRequest httpRequest) {
|
||||
this.sdkRequest = sdkRequest;
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
public KustoRequest getSdkRequest() {
|
||||
return sdkRequest;
|
||||
}
|
||||
|
||||
public void setSdkRequest(KustoRequest sdkRequest) {
|
||||
this.sdkRequest = sdkRequest;
|
||||
}
|
||||
|
||||
public HttpRequest getHttpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
public void setHttpRequest(HttpRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.microsoft.azure.kusto.data.res;
|
||||
|
||||
public class JsonResult {
|
||||
private String result;
|
||||
private String endpoint;
|
||||
|
||||
public JsonResult(String result, String endpoint) {
|
||||
this.result = result;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
public String getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public void setEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ class ClientRequestPropertiesTest {
|
|||
// before setting value should be null
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutInMilliSec());
|
||||
Object timeoutObj = clientRequestProperties.getOption(OPTION_SERVER_TIMEOUT);
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutAsString(timeoutObj));
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutAsCslTimespan(timeoutObj));
|
||||
|
||||
clientRequestProperties.setTimeoutInMilliSec(serverTimeoutOptionMillis);
|
||||
Assertions.assertEquals(clientRequestProperties.getTimeoutInMilliSec(), expectedMillis);
|
||||
|
@ -71,7 +71,7 @@ class ClientRequestPropertiesTest {
|
|||
// before setting value should be null
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutInMilliSec());
|
||||
Object timeoutObj = clientRequestProperties.getOption(OPTION_SERVER_TIMEOUT);
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutAsString(timeoutObj));
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutAsCslTimespan(timeoutObj));
|
||||
|
||||
clientRequestProperties.setOption(ClientRequestProperties.OPTION_SERVER_TIMEOUT, serverTimeoutOptionMillis);
|
||||
Assertions.assertEquals(clientRequestProperties.getTimeoutInMilliSec(), expectedMillis);
|
||||
|
@ -88,7 +88,7 @@ class ClientRequestPropertiesTest {
|
|||
// before setting value should be null
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutInMilliSec());
|
||||
Object timeoutObj = clientRequestProperties.getOption(OPTION_SERVER_TIMEOUT);
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutAsString(timeoutObj));
|
||||
Assertions.assertNull(clientRequestProperties.getTimeoutAsCslTimespan(timeoutObj));
|
||||
|
||||
clientRequestProperties.setOption(ClientRequestProperties.OPTION_SERVER_TIMEOUT, serverTimeoutOptionTimespan);
|
||||
Assertions.assertEquals(clientRequestProperties.getTimeoutInMilliSec(), expectedMillis);
|
||||
|
|
|
@ -1,33 +1,75 @@
|
|||
package com.microsoft.azure.kusto.data;
|
||||
|
||||
import com.azure.core.http.HttpHeaders;
|
||||
import com.azure.core.http.HttpRequest;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.http.HttpRequestBuilder;
|
||||
import com.microsoft.azure.kusto.data.http.HttpTracing;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HeaderTest {
|
||||
|
||||
@Test
|
||||
public void testHeadersDefault() throws URISyntaxException {
|
||||
public void testHeadersDefault() throws URISyntaxException, DataClientException {
|
||||
ConnectionStringBuilder csb = ConnectionStringBuilder.createWithAadManagedIdentity("https://testcluster.kusto.windows.net");
|
||||
|
||||
ClientImpl client = (ClientImpl) ClientFactory.createClient(csb);
|
||||
Map<String, String> headers = client.extractTracingHeaders(new ClientRequestProperties());
|
||||
|
||||
ClientRequestProperties crp = new ClientRequestProperties();
|
||||
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(crp)
|
||||
.withRequestPrefix("KJC.execute")
|
||||
.withActivitySuffix("QueryCommand")
|
||||
.withClientDetails(client.getClientDetails())
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost("https://www.example.com")
|
||||
.withTracing(tracing)
|
||||
.build();
|
||||
|
||||
Map<String, String> headers = extractHeadersFromAzureRequest(request);
|
||||
|
||||
Assertions.assertNotNull(headers.get("x-ms-app"));
|
||||
Assertions.assertNotNull(headers.get("x-ms-user"));
|
||||
Assertions.assertTrue(headers.get("x-ms-client-version").startsWith("Kusto.Java.Client"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadersWithCustomCsb() throws URISyntaxException {
|
||||
public void testHeadersWithCustomCsb() throws URISyntaxException, DataClientException {
|
||||
ConnectionStringBuilder csb = ConnectionStringBuilder.createWithAadManagedIdentity("https://testcluster.kusto.windows.net");
|
||||
csb.setApplicationNameForTracing("testApp");
|
||||
csb.setUserNameForTracing("testUser");
|
||||
csb.setClientVersionForTracing("testVersion");
|
||||
|
||||
ClientImpl client = (ClientImpl) ClientFactory.createClient(csb);
|
||||
Map<String, String> headers = client.extractTracingHeaders(new ClientRequestProperties());
|
||||
|
||||
ClientRequestProperties crp = new ClientRequestProperties();
|
||||
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(crp)
|
||||
.withRequestPrefix("KJC.execute")
|
||||
.withActivitySuffix("QueryCommand")
|
||||
.withClientDetails(client.getClientDetails())
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost("https://www.example.com")
|
||||
.withTracing(tracing)
|
||||
.build();
|
||||
|
||||
Map<String, String> headers = extractHeadersFromAzureRequest(request);
|
||||
|
||||
Assertions.assertEquals("testApp", headers.get("x-ms-app"));
|
||||
Assertions.assertEquals("testUser", headers.get("x-ms-user"));
|
||||
Assertions.assertTrue(headers.get("x-ms-client-version").startsWith("Kusto.Java.Client"));
|
||||
|
@ -35,17 +77,33 @@ public class HeaderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testHeadersWithCustomCsbAndClientRequestProperties() throws URISyntaxException {
|
||||
public void testHeadersWithCustomCsbAndClientRequestProperties() throws URISyntaxException, DataClientException {
|
||||
ConnectionStringBuilder csb = ConnectionStringBuilder.createWithAadManagedIdentity("https://testcluster.kusto.windows.net");
|
||||
csb.setApplicationNameForTracing("testApp");
|
||||
csb.setUserNameForTracing("testUser");
|
||||
csb.setClientVersionForTracing("testVersion");
|
||||
|
||||
ClientImpl client = (ClientImpl) ClientFactory.createClient(csb);
|
||||
|
||||
ClientRequestProperties crp = new ClientRequestProperties();
|
||||
crp.setApplication("crpApp");
|
||||
crp.setUser("crpUser");
|
||||
|
||||
Map<String, String> headers = client.extractTracingHeaders(crp);
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(crp)
|
||||
.withRequestPrefix("KJC.execute")
|
||||
.withActivitySuffix("QueryCommand")
|
||||
.withClientDetails(client.getClientDetails())
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost("https://www.example.com")
|
||||
.withTracing(tracing)
|
||||
.build();
|
||||
|
||||
Map<String, String> headers = extractHeadersFromAzureRequest(request);
|
||||
|
||||
Assertions.assertEquals("crpApp", headers.get("x-ms-app"));
|
||||
Assertions.assertEquals("crpUser", headers.get("x-ms-user"));
|
||||
Assertions.assertTrue(headers.get("x-ms-client-version").startsWith("Kusto.Java.Client"));
|
||||
|
@ -53,13 +111,29 @@ public class HeaderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSetConnectorNameAndVersion() throws URISyntaxException {
|
||||
public void testSetConnectorNameAndVersion() throws URISyntaxException, DataClientException {
|
||||
ConnectionStringBuilder csb = ConnectionStringBuilder.createWithAadManagedIdentity("https://testcluster.kusto.windows.net");
|
||||
csb.setConnectorDetails("myConnector", "myVersion", null, null, false, null);
|
||||
|
||||
ClientImpl client = (ClientImpl) ClientFactory.createClient(csb);
|
||||
|
||||
ClientRequestProperties crp = new ClientRequestProperties();
|
||||
|
||||
Map<String, String> headers = client.extractTracingHeaders(crp);
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(crp)
|
||||
.withRequestPrefix("KJC.execute")
|
||||
.withActivitySuffix("QueryCommand")
|
||||
.withClientDetails(client.getClientDetails())
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost("https://www.example.com")
|
||||
.withTracing(tracing)
|
||||
.build();
|
||||
|
||||
Map<String, String> headers = extractHeadersFromAzureRequest(request);
|
||||
|
||||
Assertions.assertEquals("[none]", headers.get("x-ms-user"));
|
||||
Assertions.assertTrue(headers.get("x-ms-client-version").startsWith("Kusto.Java.Client:"));
|
||||
|
||||
|
@ -67,13 +141,29 @@ public class HeaderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSetConnectorNoAppVersion() throws URISyntaxException {
|
||||
public void testSetConnectorNoAppVersion() throws URISyntaxException, DataClientException {
|
||||
ConnectionStringBuilder csb = ConnectionStringBuilder.createWithAadManagedIdentity("https://testcluster.kusto.windows.net");
|
||||
csb.setConnectorDetails("myConnector", "myVersion", null, null, true, "myApp");
|
||||
|
||||
ClientImpl client = (ClientImpl) ClientFactory.createClient(csb);
|
||||
|
||||
ClientRequestProperties crp = new ClientRequestProperties();
|
||||
|
||||
Map<String, String> headers = client.extractTracingHeaders(crp);
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(crp)
|
||||
.withRequestPrefix("KJC.execute")
|
||||
.withActivitySuffix("QueryCommand")
|
||||
.withClientDetails(client.getClientDetails())
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost("https://www.example.com")
|
||||
.withTracing(tracing)
|
||||
.build();
|
||||
|
||||
Map<String, String> headers = extractHeadersFromAzureRequest(request);
|
||||
|
||||
Assertions.assertTrue(headers.get("x-ms-user").length() > 0);
|
||||
Assertions.assertTrue(headers.get("x-ms-client-version").startsWith("Kusto.Java.Client:"));
|
||||
|
||||
|
@ -81,16 +171,40 @@ public class HeaderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSetConnectorFull() throws URISyntaxException {
|
||||
public void testSetConnectorFull() throws URISyntaxException, DataClientException {
|
||||
ConnectionStringBuilder csb = ConnectionStringBuilder.createWithAadManagedIdentity("https://testcluster.kusto.windows.net");
|
||||
csb.setConnectorDetails("myConnector", "myVersion", "myApp", "myAppVersion", true, "myUser", Pair.of("myField", "myValue"));
|
||||
|
||||
ClientImpl client = (ClientImpl) ClientFactory.createClient(csb);
|
||||
|
||||
ClientRequestProperties crp = new ClientRequestProperties();
|
||||
|
||||
Map<String, String> headers = client.extractTracingHeaders(crp);
|
||||
HttpTracing tracing = HttpTracing
|
||||
.newBuilder()
|
||||
.withProperties(crp)
|
||||
.withRequestPrefix("KJC.execute")
|
||||
.withActivitySuffix("QueryCommand")
|
||||
.withClientDetails(client.getClientDetails())
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequestBuilder
|
||||
.newPost("https://www.example.com")
|
||||
.withTracing(tracing)
|
||||
.build();
|
||||
|
||||
Map<String, String> headers = extractHeadersFromAzureRequest(request);
|
||||
|
||||
Assertions.assertEquals("myUser", headers.get("x-ms-user"));
|
||||
Assertions.assertTrue(headers.get("x-ms-client-version").startsWith("Kusto.Java.Client:"));
|
||||
|
||||
Assertions.assertEquals("Kusto.myConnector:{myVersion}|App.{myApp}:{myAppVersion}|myField:{myValue}", headers.get("x-ms-app"));
|
||||
}
|
||||
|
||||
private Map<String, String> extractHeadersFromAzureRequest(HttpRequest request) {
|
||||
Map<String, String> uncomplicatedHeaders = new HashMap<>();
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
headers.forEach(header -> uncomplicatedHeaders.put(header.getName(), header.getValue()));
|
||||
return uncomplicatedHeaders;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package com.microsoft.azure.kusto.data;
|
||||
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataWebException;
|
||||
import com.microsoft.azure.kusto.data.http.HttpPostUtils;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
|
||||
import com.microsoft.azure.kusto.data.http.HttpStatus;
|
||||
import com.microsoft.azure.kusto.data.http.TestHttpResponse;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
|
@ -22,7 +22,7 @@ class UtilitiesTest {
|
|||
@Test
|
||||
@DisplayName("Test exception creation when the web response is null")
|
||||
void createExceptionFromResponseNoResponse() {
|
||||
DataServiceException error = HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", null, new Exception(), "error");
|
||||
DataServiceException error = BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", null, new Exception(), "error");
|
||||
Assertions.assertEquals("POST failed to send request", error.getMessage());
|
||||
Assertions.assertFalse(error.isPermanent());
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ class UtilitiesTest {
|
|||
@Test
|
||||
@DisplayName("Test exception creation on a 404 error")
|
||||
void createExceptionFromResponse404Error() {
|
||||
BasicHttpResponse basicHttpResponse = getBasicHttpResponse(404);
|
||||
DataServiceException error = HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(), "error");
|
||||
Assertions.assertTrue(error.getStatusCode() != null && error.getStatusCode() == HttpStatus.SC_NOT_FOUND);
|
||||
HttpResponse basicHttpResponse = getHttpResponse(HttpStatus.NOT_FOUND);
|
||||
DataServiceException error = BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(), "error");
|
||||
Assertions.assertTrue(error.getStatusCode() != null && error.getStatusCode() == HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -60,61 +60,61 @@ class UtilitiesTest {
|
|||
" },\n" +
|
||||
" \"@permanent\": true\n" +
|
||||
" }}";
|
||||
BasicHttpResponse basicHttpResponse = getBasicHttpResponse(401);
|
||||
DataServiceException error = HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
HttpResponse basicHttpResponse = getHttpResponse(HttpStatus.UNAUTHORIZED);
|
||||
DataServiceException error = BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
OneApiError);
|
||||
Assertions.assertEquals("Query execution has exceeded the allowed limits (80DA0003): ., ActivityId='1234'", error.getMessage());
|
||||
Assertions.assertTrue(error.getCause() instanceof DataWebException);
|
||||
Assertions.assertInstanceOf(DataWebException.class, error.getCause());
|
||||
Assertions.assertTrue(error.isPermanent());
|
||||
Assertions.assertEquals(401, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
Assertions.assertEquals(HttpStatus.UNAUTHORIZED, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test exception creation from a message object")
|
||||
void createExceptionFromMessageError() {
|
||||
String errorMessage = "{\"message\": \"Test Error Message\"}";
|
||||
BasicHttpResponse basicHttpResponse = getBasicHttpResponse(401);
|
||||
DataServiceException error = HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
HttpResponse basicHttpResponse = getHttpResponse(HttpStatus.UNAUTHORIZED);
|
||||
DataServiceException error = BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
errorMessage);
|
||||
Assertions.assertEquals("Test Error Message, ActivityId='1234'", error.getMessage());
|
||||
Assertions.assertFalse(error.isPermanent());
|
||||
Assertions.assertEquals(401, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
Assertions.assertEquals(HttpStatus.UNAUTHORIZED, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test exception creation from a bad json")
|
||||
void createExceptionFromBadJson() {
|
||||
String errorMessage = "\"message\": \"Test Error Message\"";
|
||||
BasicHttpResponse basicHttpResponse = getBasicHttpResponse(401);
|
||||
DataServiceException error = HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
HttpResponse basicHttpResponse = getHttpResponse(HttpStatus.UNAUTHORIZED);
|
||||
DataServiceException error = BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
errorMessage);
|
||||
Assertions.assertEquals("\"message\": \"Test Error Message\", ActivityId='1234'", error.getMessage());
|
||||
Assertions.assertFalse(error.isPermanent());
|
||||
Assertions.assertEquals(401, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
Assertions.assertEquals(HttpStatus.UNAUTHORIZED, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test exception creation from an unexpected json")
|
||||
void createExceptionFromOtherJson() {
|
||||
String errorMessage = "{\"response\": \"Test Error Message\"}";
|
||||
BasicHttpResponse basicHttpResponse = getBasicHttpResponse(401);
|
||||
DataServiceException error = HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
HttpResponse basicHttpResponse = getHttpResponse(HttpStatus.UNAUTHORIZED);
|
||||
DataServiceException error = BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
errorMessage);
|
||||
Assertions.assertEquals("{\"response\": \"Test Error Message\"}, ActivityId='1234'", error.getMessage());
|
||||
Assertions.assertFalse(error.isPermanent());
|
||||
Assertions.assertEquals(401, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
Assertions.assertEquals(HttpStatus.UNAUTHORIZED, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test exception creation from a blank error message")
|
||||
void createExceptionFromBlankErrorMessage() {
|
||||
String errorMessage = " ";
|
||||
BasicHttpResponse basicHttpResponse = getBasicHttpResponse(401);
|
||||
DataServiceException error = HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
HttpResponse basicHttpResponse = getHttpResponse(HttpStatus.UNAUTHORIZED);
|
||||
DataServiceException error = BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", basicHttpResponse, new Exception(),
|
||||
errorMessage);
|
||||
Assertions.assertEquals("Http StatusCode='http/1.1 401 Some Error', ActivityId='1234'", error.getMessage());
|
||||
Assertions.assertEquals("Http StatusCode='401', ActivityId='1234'", error.getMessage());
|
||||
Assertions.assertFalse(error.isPermanent());
|
||||
Assertions.assertEquals(401, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
Assertions.assertEquals(HttpStatus.UNAUTHORIZED, Objects.requireNonNull(error.getStatusCode()).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -126,7 +126,7 @@ class UtilitiesTest {
|
|||
@Test
|
||||
@DisplayName("Assert file name extracted from some cmd line")
|
||||
void extractFileNameFromCommandLine() {
|
||||
String cmdLine = Paths.get(" home", "user", "someFile.jar").toString() + " -arg1 val";
|
||||
String cmdLine = Paths.get(" home", "user", "someFile.jar") + " -arg1 val";
|
||||
Assertions.assertEquals(UriUtils.stripFileNameFromCommandLine(cmdLine), "someFile.jar");
|
||||
}
|
||||
|
||||
|
@ -136,17 +136,19 @@ class UtilitiesTest {
|
|||
IOException e = new UnknownHostException("Doesnt exist");
|
||||
Assertions.assertFalse(Utils.isRetriableIOException(e));
|
||||
|
||||
e = new org.apache.http.conn.HttpHostConnectException(new ConnectException("Connection refused"), null);
|
||||
e = new ConnectException("Connection refused");
|
||||
Assertions.assertFalse(Utils.isRetriableIOException(e));
|
||||
|
||||
e = new org.apache.http.conn.HttpHostConnectException(new ConnectException("Connection timed out"), null);
|
||||
e = new ConnectException("Connection timed out");
|
||||
Assertions.assertTrue(Utils.isRetriableIOException(e));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private BasicHttpResponse getBasicHttpResponse(int statusCode) {
|
||||
BasicHttpResponse basicHttpResponse = new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion("http", 1, 1), statusCode, "Some Error"));
|
||||
basicHttpResponse.addHeader("x-ms-activity-id", "1234");
|
||||
return basicHttpResponse;
|
||||
private HttpResponse getHttpResponse(int statusCode) {
|
||||
return TestHttpResponse
|
||||
.newBuilder()
|
||||
.withStatusCode(statusCode)
|
||||
.addHeader("x-ms-activity-id", "1234")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import com.azure.core.http.HttpClient;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -17,7 +17,7 @@ class HttpClientFactoryTest {
|
|||
@DisplayName("test create http client from properties")
|
||||
void testProperties() {
|
||||
HttpClientProperties properties = HttpClientProperties.builder().build();
|
||||
final CloseableHttpClient httpClient = HttpClientFactory.create(properties);
|
||||
final HttpClient httpClient = HttpClientFactory.create(properties);
|
||||
Assertions.assertNotNull(httpClient);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package com.microsoft.azure.kusto.data.http;
|
||||
|
||||
import com.azure.core.http.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class TestHttpResponse extends HttpResponse {
|
||||
|
||||
private int fakeStatusCode;
|
||||
private final HttpHeaders fakeHeaders = new HttpHeaders();
|
||||
|
||||
private TestHttpResponse() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
protected TestHttpResponse(HttpRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatusCode() {
|
||||
return fakeStatusCode;
|
||||
}
|
||||
|
||||
private void setStatusCode(int statusCode) {
|
||||
this.fakeStatusCode = statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeaderValue(String s) {
|
||||
return fakeHeaders.getValue(HttpHeaderName.fromString(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return fakeHeaders;
|
||||
}
|
||||
|
||||
private void addHeader(String name, String value) {
|
||||
fakeHeaders.add(HttpHeaderName.fromString(name), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ByteBuffer> getBody() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<byte[]> getBodyAsByteArray() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getBodyAsString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getBodyAsString(Charset charset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TestHttpResponseBuilder newBuilder() {
|
||||
return new TestHttpResponseBuilder();
|
||||
}
|
||||
|
||||
public static class TestHttpResponseBuilder {
|
||||
|
||||
private final TestHttpResponse res = new TestHttpResponse();
|
||||
|
||||
private TestHttpResponseBuilder() {
|
||||
}
|
||||
|
||||
public TestHttpResponseBuilder withStatusCode(int statusCode) {
|
||||
res.setStatusCode(statusCode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestHttpResponseBuilder addHeader(String name, String value) {
|
||||
res.addHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestHttpResponse build() {
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -105,9 +105,14 @@
|
|||
<dependency>org.xerial:sqlite-jdbc</dependency>
|
||||
</ignoredUnusedDeclaredDependencies>
|
||||
<ignoredUsedUndeclaredDependencies>
|
||||
<dependency>org.reactivestreams:reactive-streams:jar</dependency>
|
||||
<dependency>com.microsoft.azure:msal4j:jar</dependency>
|
||||
<dependency>io.projectreactor:reactor-core:jar</dependency>
|
||||
</ignoredUsedUndeclaredDependencies>
|
||||
<ignoreNonCompile>true</ignoreNonCompile>
|
||||
<ignoredNonTestScopedDependencies>
|
||||
<dependency>org.reactivestreams:reactive-streams:jar</dependency>
|
||||
</ignoredNonTestScopedDependencies>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -172,10 +177,6 @@
|
|||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.azure</groupId>
|
||||
<artifactId>azure-core-http-netty</artifactId>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
|
@ -206,7 +207,7 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
<version>${apache.httpclient.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
|
@ -218,12 +219,6 @@
|
|||
<artifactId>univocity-parsers</artifactId>
|
||||
<version>${univocity-parsers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>${httpcore.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
|
@ -264,11 +259,6 @@
|
|||
<artifactId>commons-codec</artifactId>
|
||||
<version>${commons-codec.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>${reactor-core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.resilience4j</groupId>
|
||||
<artifactId>resilience4j-retry</artifactId>
|
||||
|
@ -279,5 +269,11 @@
|
|||
<artifactId>vavr</artifactId>
|
||||
<version>${io.vavr.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.azure.kusto</groupId>
|
||||
<artifactId>kusto-data</artifactId>
|
||||
<version>6.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
@ -226,7 +226,7 @@ public class IngestClientFactory {
|
|||
* @throws URISyntaxException if the connection string is invalid
|
||||
*/
|
||||
public static ManagedStreamingIngestClient createManagedStreamingIngestClientFromDmCsb(ConnectionStringBuilder connectionStringBuilder,
|
||||
@Nullable CloseableHttpClient httpClient, boolean autoCorrectEndpoint)
|
||||
@Nullable HttpClient httpClient, boolean autoCorrectEndpoint)
|
||||
throws URISyntaxException {
|
||||
return new ManagedStreamingIngestClient(connectionStringBuilder, httpClient, autoCorrectEndpoint);
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ public class IngestClientFactory {
|
|||
* @throws URISyntaxException if the connection string is invalid
|
||||
*/
|
||||
public static ManagedStreamingIngestClient createManagedStreamingIngestClientFromDmCsb(ConnectionStringBuilder connectionStringBuilder,
|
||||
@Nullable CloseableHttpClient httpClient)
|
||||
@Nullable HttpClient httpClient)
|
||||
throws URISyntaxException {
|
||||
return new ManagedStreamingIngestClient(connectionStringBuilder, httpClient, true);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import com.azure.storage.blob.BlobClientBuilder;
|
|||
import com.azure.storage.blob.models.BlobStorageException;
|
||||
import com.azure.storage.common.policy.RequestRetryOptions;
|
||||
import com.microsoft.azure.kusto.data.Ensure;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.StreamingClient;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
|
@ -19,7 +19,6 @@ import com.microsoft.azure.kusto.ingest.source.*;
|
|||
import com.microsoft.azure.kusto.data.ExponentialRetry;
|
||||
|
||||
import com.microsoft.azure.kusto.ingest.utils.IngestionUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -51,7 +50,7 @@ public class ManagedStreamingIngestClient extends IngestClientBase implements Qu
|
|||
final QueuedIngestClientImpl queuedIngestClient;
|
||||
final StreamingIngestClient streamingIngestClient;
|
||||
private final ExponentialRetry exponentialRetryTemplate;
|
||||
private CloseableHttpClient httpClient = null;
|
||||
private HttpClient httpClient = null;
|
||||
|
||||
/**
|
||||
* @param dmConnectionString dm connection string
|
||||
|
@ -179,7 +178,7 @@ public class ManagedStreamingIngestClient extends IngestClientBase implements Qu
|
|||
* @throws URISyntaxException if the connection string is invalid
|
||||
*/
|
||||
public ManagedStreamingIngestClient(ConnectionStringBuilder connectionStringBuilder,
|
||||
@Nullable CloseableHttpClient httpClient, boolean autoCorrectEndpoint) throws URISyntaxException {
|
||||
@Nullable HttpClient httpClient, boolean autoCorrectEndpoint) throws URISyntaxException {
|
||||
log.info("Creating a new ManagedStreamingIngestClient from connection strings");
|
||||
queuedIngestClient = new QueuedIngestClientImpl(connectionStringBuilder, httpClient, autoCorrectEndpoint);
|
||||
streamingIngestClient = new StreamingIngestClient(connectionStringBuilder, httpClient, autoCorrectEndpoint);
|
||||
|
@ -221,7 +220,7 @@ public class ManagedStreamingIngestClient extends IngestClientBase implements Qu
|
|||
* @throws URISyntaxException if the connection string is invalid
|
||||
*/
|
||||
public ManagedStreamingIngestClient(ConnectionStringBuilder connectionStringBuilder,
|
||||
@Nullable CloseableHttpClient httpClient) throws URISyntaxException {
|
||||
@Nullable HttpClient httpClient) throws URISyntaxException {
|
||||
log.info("Creating a new ManagedStreamingIngestClient from connection strings");
|
||||
queuedIngestClient = new QueuedIngestClientImpl(connectionStringBuilder, httpClient, true);
|
||||
streamingIngestClient = new StreamingIngestClient(connectionStringBuilder, httpClient, true);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.data.tables.models.TableEntity;
|
||||
import com.azure.data.tables.models.TableServiceException;
|
||||
import com.azure.storage.blob.models.BlobStorageException;
|
||||
|
@ -11,7 +12,7 @@ import com.azure.storage.queue.models.QueueStorageException;
|
|||
import com.microsoft.azure.kusto.data.*;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientFactory;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.ingest.exceptions.IngestionClientException;
|
||||
import com.microsoft.azure.kusto.ingest.exceptions.IngestionServiceException;
|
||||
import com.microsoft.azure.kusto.ingest.result.*;
|
||||
|
@ -20,7 +21,6 @@ import com.microsoft.azure.kusto.ingest.utils.IngestionUtils;
|
|||
import com.microsoft.azure.kusto.ingest.utils.SecurityUtils;
|
||||
import com.microsoft.azure.kusto.ingest.utils.TableWithSas;
|
||||
import com.univocity.parsers.csv.CsvRoutines;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -51,7 +51,7 @@ public class QueuedIngestClientImpl extends IngestClientBase implements QueuedIn
|
|||
this(csb, properties == null ? null : HttpClientFactory.create(properties), autoCorrectEndpoint);
|
||||
}
|
||||
|
||||
QueuedIngestClientImpl(ConnectionStringBuilder csb, CloseableHttpClient httpClient, boolean autoCorrectEndpoint) throws URISyntaxException {
|
||||
QueuedIngestClientImpl(ConnectionStringBuilder csb, HttpClient httpClient, boolean autoCorrectEndpoint) throws URISyntaxException {
|
||||
log.info("Creating a new IngestClient");
|
||||
ConnectionStringBuilder csbWithEndpoint = new ConnectionStringBuilder(csb);
|
||||
csbWithEndpoint.setClusterUrl(autoCorrectEndpoint ? getIngestionEndpoint(csbWithEndpoint.getClusterUrl()) : csbWithEndpoint.getClusterUrl());
|
||||
|
@ -146,8 +146,6 @@ public class QueuedIngestClientImpl extends IngestClientBase implements QueuedIn
|
|||
throw new IngestionServiceException("Failed to ingest from blob", e);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new IngestionClientException("Failed to ingest from blob", e);
|
||||
} catch (IngestionServiceException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,8 +186,6 @@ public class QueuedIngestClientImpl extends IngestClientBase implements QueuedIn
|
|||
throw new IngestionServiceException("Failed to ingest from file", e);
|
||||
} catch (IOException e) {
|
||||
throw new IngestionClientException("Failed to ingest from file", e);
|
||||
} catch (IngestionServiceException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,8 +234,6 @@ public class QueuedIngestClientImpl extends IngestClientBase implements QueuedIn
|
|||
throw new IngestionServiceException("Failed to ingest from stream", e);
|
||||
} catch (IOException e) {
|
||||
throw new IngestionClientException("Failed to ingest from stream", e);
|
||||
} catch (IngestionServiceException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
|
||||
import com.azure.storage.common.policy.RequestRetryOptions;
|
||||
import com.microsoft.azure.kusto.data.Client;
|
||||
import com.microsoft.azure.kusto.data.KustoOperationResult;
|
||||
import com.microsoft.azure.kusto.data.KustoResultSetTable;
|
||||
import com.microsoft.azure.kusto.data.Utils;
|
||||
import com.microsoft.azure.kusto.data.auth.HttpClientWrapper;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.ThrottleException;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientFactory;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.MonitoredActivity;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.SupplierTwoExceptions;
|
||||
import com.microsoft.azure.kusto.ingest.exceptions.IngestionClientException;
|
||||
|
@ -27,13 +27,11 @@ import com.microsoft.azure.kusto.ingest.utils.TableWithSas;
|
|||
import io.github.resilience4j.retry.Retry;
|
||||
import io.github.resilience4j.retry.RetryConfig;
|
||||
import io.vavr.CheckedFunction0;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.util.annotation.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Duration;
|
||||
|
@ -72,12 +70,12 @@ class ResourceManager implements Closeable, IngestionResourceManager {
|
|||
protected RefreshIngestionAuthTokenTask refreshIngestionAuthTokenTask;
|
||||
protected RefreshIngestionResourcesTask refreshIngestionResourcesTask;
|
||||
|
||||
public ResourceManager(Client client, long defaultRefreshTime, long refreshTimeOnFailure, @Nullable CloseableHttpClient httpClient) {
|
||||
public ResourceManager(Client client, long defaultRefreshTime, long refreshTimeOnFailure, @Nullable HttpClient httpClient) {
|
||||
this.client = client;
|
||||
// Using ctor with client so that the dependency is used
|
||||
this.httpClient = httpClient == null
|
||||
? new NettyAsyncHttpClientBuilder().responseTimeout(Duration.ofMinutes(UPLOAD_TIMEOUT_MINUTES)).build()
|
||||
: new HttpClientWrapper(httpClient);
|
||||
? HttpClientFactory.create(HttpClientProperties.builder().timeout(Duration.ofMinutes(UPLOAD_TIMEOUT_MINUTES)).build())
|
||||
: httpClient;
|
||||
|
||||
// Refresh tasks
|
||||
this.refreshTasksTimer = new Timer(true);
|
||||
|
@ -89,7 +87,7 @@ class ResourceManager implements Closeable, IngestionResourceManager {
|
|||
this.storageAccountSet = new RankedStorageAccountSet();
|
||||
}
|
||||
|
||||
public ResourceManager(Client client, @Nullable CloseableHttpClient httpClient) {
|
||||
public ResourceManager(Client client, @Nullable HttpClient httpClient) {
|
||||
this(client, REFRESH_INGESTION_RESOURCES_PERIOD, REFRESH_INGESTION_RESOURCES_PERIOD_ON_FAILURE, httpClient);
|
||||
}
|
||||
|
||||
|
@ -98,11 +96,6 @@ class ResourceManager implements Closeable, IngestionResourceManager {
|
|||
refreshTasksTimer.cancel();
|
||||
refreshTasksTimer.purge();
|
||||
refreshTasksTimer = null;
|
||||
try {
|
||||
client.close();
|
||||
} catch (IOException e) {
|
||||
log.error("Couldn't close client: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class RefreshResourceTask extends TimerTask {
|
||||
|
@ -150,7 +143,7 @@ class ResourceManager implements Closeable, IngestionResourceManager {
|
|||
IngestionResourceSet newIngestionResourceSet = new IngestionResourceSet();
|
||||
Retry retry = Retry.of("get ingestion resources", taskRetryConfig);
|
||||
CheckedFunction0<KustoOperationResult> retryExecute = Retry.decorateCheckedSupplier(retry,
|
||||
() -> client.execute(Commands.INGESTION_RESOURCES_SHOW_COMMAND));
|
||||
() -> client.executeMgmt(Commands.INGESTION_RESOURCES_SHOW_COMMAND));
|
||||
KustoOperationResult ingestionResourcesResults = retryExecute.apply();
|
||||
if (ingestionResourcesResults != null) {
|
||||
KustoResultSetTable table = ingestionResourcesResults.getPrimaryResults();
|
||||
|
@ -246,7 +239,7 @@ class ResourceManager implements Closeable, IngestionResourceManager {
|
|||
log.info("Refreshing Ingestion Auth Token");
|
||||
Retry retry = Retry.of("get Ingestion Auth Token resources", taskRetryConfig);
|
||||
CheckedFunction0<KustoOperationResult> retryExecute = Retry.decorateCheckedSupplier(retry,
|
||||
() -> client.execute(Commands.IDENTITY_GET_COMMAND));
|
||||
() -> client.executeMgmt(Commands.IDENTITY_GET_COMMAND));
|
||||
KustoOperationResult identityTokenResult = retryExecute.apply();
|
||||
if (identityTokenResult != null
|
||||
&& identityTokenResult.hasNext()
|
||||
|
@ -397,10 +390,10 @@ class ResourceManager implements Closeable, IngestionResourceManager {
|
|||
@Override
|
||||
public void reportIngestionResult(ResourceWithSas<?> resource, boolean success) {
|
||||
if (storageAccountSet == null) {
|
||||
log.error("StorageAccountSet is null, so can't report ingestion result");
|
||||
} else {
|
||||
storageAccountSet.addResultToAccount(resource.getAccountName(), success);
|
||||
log.warn("StorageAccountSet is null");
|
||||
return;
|
||||
}
|
||||
storageAccountSet.addResultToAccount(resource.getAccountName(), success);
|
||||
}
|
||||
|
||||
enum ResourceType {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.storage.blob.BlobClient;
|
||||
import com.azure.storage.blob.BlobClientBuilder;
|
||||
import com.azure.storage.blob.models.BlobStorageException;
|
||||
|
@ -10,7 +11,7 @@ import com.microsoft.azure.kusto.data.*;
|
|||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.MonitoredActivity;
|
||||
import com.microsoft.azure.kusto.data.instrumentation.SupplierTwoExceptions;
|
||||
import com.microsoft.azure.kusto.ingest.exceptions.IngestionClientException;
|
||||
|
@ -26,7 +27,6 @@ import com.microsoft.azure.kusto.ingest.source.StreamSourceInfo;
|
|||
import com.microsoft.azure.kusto.ingest.utils.IngestionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -52,7 +52,7 @@ public class StreamingIngestClient extends IngestClientBase implements IngestCli
|
|||
this.connectionDataSource = csbWithEndpoint.getClusterUrl();
|
||||
}
|
||||
|
||||
StreamingIngestClient(ConnectionStringBuilder csb, @Nullable CloseableHttpClient httpClient, boolean autoCorrectEndpoint) throws URISyntaxException {
|
||||
StreamingIngestClient(ConnectionStringBuilder csb, @Nullable HttpClient httpClient, boolean autoCorrectEndpoint) throws URISyntaxException {
|
||||
log.info("Creating a new StreamingIngestClient");
|
||||
ConnectionStringBuilder csbWithEndpoint = new ConnectionStringBuilder(csb);
|
||||
csbWithEndpoint.setClusterUrl(autoCorrectEndpoint ? getQueryEndpoint(csbWithEndpoint.getClusterUrl()) : csbWithEndpoint.getClusterUrl());
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import com.azure.core.http.HttpClient;
|
||||
import com.azure.core.http.HttpResponse;
|
||||
import com.azure.core.util.Context;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
|
@ -15,7 +18,8 @@ import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
|||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import com.microsoft.azure.kusto.data.format.CslDateTimeFormat;
|
||||
import com.microsoft.azure.kusto.data.format.CslTimespanFormat;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientFactory;
|
||||
import com.microsoft.azure.kusto.ingest.IngestionMapping.IngestionMappingKind;
|
||||
import com.microsoft.azure.kusto.ingest.IngestionProperties.DataFormat;
|
||||
import com.microsoft.azure.kusto.ingest.exceptions.IngestionClientException;
|
||||
|
@ -28,9 +32,8 @@ import com.microsoft.azure.kusto.ingest.source.StreamSourceInfo;
|
|||
import com.microsoft.azure.kusto.ingest.utils.SecurityUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.StopWatch;
|
||||
|
||||
import org.apache.http.conn.util.InetAddressUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
@ -38,7 +41,6 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
@ -57,20 +59,35 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||
import static com.microsoft.azure.kusto.ingest.IngestClientBase.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class E2ETest {
|
||||
|
||||
private static IngestClient ingestClient;
|
||||
private static StreamingIngestClient streamingIngestClient;
|
||||
private static ManagedStreamingIngestClient managedStreamingIngestClient;
|
||||
private static Client queryClient;
|
||||
private static Client dmCslClient;
|
||||
private static StreamingClient streamingClient;
|
||||
private static final String databaseName = System.getenv("TEST_DATABASE");
|
||||
private static final String appId = System.getenv("APP_ID");
|
||||
private static final String appKey = System.getenv("APP_KEY");
|
||||
private static final String tenantId = System.getenv().getOrDefault("TENANT_ID", "microsoft.com");
|
||||
private static ConnectionStringBuilder engineCsb;
|
||||
|
||||
// IN ORDER TO RUN THESE E2E TESTS YOU NEED THESE ENVIRONMENT VARIABLES
|
||||
|
||||
// Your test database created in the Azure Portal or via IaC
|
||||
private static final String DB_NAME = System.getenv("TEST_DATABASE");
|
||||
|
||||
private static final String APP_ID = System.getenv("APP_ID");
|
||||
private static final String APP_KEY = System.getenv("APP_KEY");
|
||||
private static final String TENANT_ID = System.getenv().getOrDefault("TENANT_ID", "microsoft.com");
|
||||
private static final String DM_CONN_STR = System.getenv("DM_CONNECTION_STRING");
|
||||
private static final String ENG_CONN_STR = System.getenv("ENGINE_CONNECTION_STRING");
|
||||
private static final String UNAME_HINT = System.getenv("USERNAME_HINT");
|
||||
private static final String TOKEN = System.getenv("TOKEN");
|
||||
private static final String PUBLIC_X509CER_FILE_LOC = System.getenv("PUBLIC_X509CER_FILE_LOC");
|
||||
private static final String PRIVATE_PKCS8_FILE_LOC = System.getenv("PRIVATE_PKCS8_FILE_LOC");
|
||||
private static final String CI_EXECUTION = System.getenv("CI_EXECUTION");
|
||||
|
||||
private static String principalFqn;
|
||||
private static String resourcesPath;
|
||||
private static int currentCount = 0;
|
||||
|
@ -84,9 +101,9 @@ class E2ETest {
|
|||
public static void setUp() {
|
||||
tableName = "JavaTest_" + new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss_SSS").format(Calendar.getInstance().getTime()) + "_"
|
||||
+ ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
|
||||
principalFqn = String.format("aadapp=%s;%s", appId, tenantId);
|
||||
principalFqn = String.format("aadapp=%s;%s", APP_ID, TENANT_ID);
|
||||
|
||||
ConnectionStringBuilder dmCsb = createConnection(System.getenv("DM_CONNECTION_STRING"));
|
||||
ConnectionStringBuilder dmCsb = createConnection(DM_CONN_STR);
|
||||
dmCsb.setUserNameForTracing("testUser");
|
||||
try {
|
||||
dmCslClient = ClientFactory.createClient(dmCsb);
|
||||
|
@ -98,10 +115,11 @@ class E2ETest {
|
|||
.maxConnectionsTotal(50)
|
||||
.build());
|
||||
} catch (URISyntaxException ex) {
|
||||
|
||||
Assertions.fail("Failed to create ingest client", ex);
|
||||
}
|
||||
|
||||
engineCsb = createConnection(System.getenv("ENGINE_CONNECTION_STRING"));
|
||||
ConnectionStringBuilder engineCsb = createConnection(ENG_CONN_STR);
|
||||
engineCsb.setUserNameForTracing("Java_E2ETest_ø");
|
||||
try {
|
||||
streamingIngestClient = IngestClientFactory.createStreamingIngestClient(engineCsb);
|
||||
|
@ -117,18 +135,16 @@ class E2ETest {
|
|||
}
|
||||
|
||||
private static @NotNull ConnectionStringBuilder createConnection(String connectionString) {
|
||||
if (appKey == null || appKey.isEmpty()) {
|
||||
if (APP_KEY == null || APP_KEY.isEmpty()) {
|
||||
return ConnectionStringBuilder.createWithAzureCli(connectionString);
|
||||
}
|
||||
|
||||
return ConnectionStringBuilder.createWithAadApplicationCredentials(connectionString, appId, appKey,
|
||||
tenantId);
|
||||
return ConnectionStringBuilder.createWithAadApplicationCredentials(connectionString, APP_ID, APP_KEY, TENANT_ID);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
try {
|
||||
queryClient.executeToJsonResult(databaseName, String.format(".drop table %s ifexists skip-seal", tableName));
|
||||
queryClient.executeToJsonResult(DB_NAME, String.format(".drop table %s ifexists skip-seal", tableName));
|
||||
ingestClient.close();
|
||||
managedStreamingIngestClient.close();
|
||||
} catch (Exception ex) {
|
||||
|
@ -137,18 +153,17 @@ class E2ETest {
|
|||
}
|
||||
|
||||
private static boolean isManualExecution() {
|
||||
return false;
|
||||
// return System.getenv("CI_EXECUTION") == null || !System.getenv("CI_EXECUTION").equals("1");
|
||||
return CI_EXECUTION == null || !CI_EXECUTION.equals("1");
|
||||
}
|
||||
|
||||
private static void createTableAndMapping() {
|
||||
try {
|
||||
queryClient.executeToJsonResult(databaseName, String.format(".drop table %s ifexists", tableName));
|
||||
queryClient.executeToJsonResult(databaseName, String.format(".create table %s %s", tableName, tableColumns));
|
||||
queryClient.executeToJsonResult(DB_NAME, String.format(".drop table %s ifexists", tableName));
|
||||
queryClient.executeToJsonResult(DB_NAME, String.format(".create table %s %s", tableName, tableColumns));
|
||||
LocalDateTime time = LocalDateTime.ofInstant(Instant.now(), ZoneId.of("UTC")).plusDays(1);
|
||||
String expiryDate = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time);
|
||||
String autoDeletePolicy = "@'{ \"ExpiryDate\" : \"" + expiryDate + "\", \"DeleteIfNotEmpty\": true }'";
|
||||
queryClient.executeToJsonResult(databaseName, String.format(".alter table %s policy auto_delete %s", tableName, autoDeletePolicy));
|
||||
queryClient.executeToJsonResult(DB_NAME, String.format(".alter table %s policy auto_delete %s", tableName, autoDeletePolicy));
|
||||
} catch (Exception ex) {
|
||||
Assertions.fail("Failed to drop and create new table", ex);
|
||||
}
|
||||
|
@ -156,35 +171,35 @@ class E2ETest {
|
|||
resourcesPath = Paths.get(System.getProperty("user.dir"), "src", "test", "resources").toString();
|
||||
try {
|
||||
String mappingAsString = new String(Files.readAllBytes(Paths.get(resourcesPath, "dataset_mapping.json")));
|
||||
queryClient.executeToJsonResult(databaseName, String.format(".create table %s ingestion json mapping '%s' '%s'",
|
||||
queryClient.executeToJsonResult(DB_NAME, String.format(".create table %s ingestion json mapping '%s' '%s'",
|
||||
tableName, mappingReference, mappingAsString));
|
||||
} catch (Exception ex) {
|
||||
Assertions.fail("Failed to create ingestion mapping", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
queryClient.executeToJsonResult(databaseName, ".clear database cache streamingingestion schema");
|
||||
queryClient.executeToJsonResult(DB_NAME, ".clear database cache streamingingestion schema");
|
||||
} catch (Exception ex) {
|
||||
Assertions.fail("Failed to refresh cache", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createTestData() {
|
||||
IngestionProperties ingestionPropertiesWithoutMapping = new IngestionProperties(databaseName, tableName);
|
||||
IngestionProperties ingestionPropertiesWithoutMapping = new IngestionProperties(DB_NAME, tableName);
|
||||
ingestionPropertiesWithoutMapping.setFlushImmediately(true);
|
||||
ingestionPropertiesWithoutMapping.setDataFormat(DataFormat.CSV);
|
||||
|
||||
IngestionProperties ingestionPropertiesWithIgnoreFirstRecord = new IngestionProperties(databaseName, tableName);
|
||||
IngestionProperties ingestionPropertiesWithIgnoreFirstRecord = new IngestionProperties(DB_NAME, tableName);
|
||||
ingestionPropertiesWithIgnoreFirstRecord.setFlushImmediately(true);
|
||||
ingestionPropertiesWithIgnoreFirstRecord.setDataFormat(DataFormat.CSV);
|
||||
ingestionPropertiesWithIgnoreFirstRecord.setIgnoreFirstRecord(true);
|
||||
|
||||
IngestionProperties ingestionPropertiesWithMappingReference = new IngestionProperties(databaseName, tableName);
|
||||
IngestionProperties ingestionPropertiesWithMappingReference = new IngestionProperties(DB_NAME, tableName);
|
||||
ingestionPropertiesWithMappingReference.setFlushImmediately(true);
|
||||
ingestionPropertiesWithMappingReference.setIngestionMapping(mappingReference, IngestionMappingKind.JSON);
|
||||
ingestionPropertiesWithMappingReference.setDataFormat(DataFormat.JSON);
|
||||
|
||||
IngestionProperties ingestionPropertiesWithColumnMapping = new IngestionProperties(databaseName, tableName);
|
||||
IngestionProperties ingestionPropertiesWithColumnMapping = new IngestionProperties(DB_NAME, tableName);
|
||||
ingestionPropertiesWithColumnMapping.setFlushImmediately(true);
|
||||
ingestionPropertiesWithColumnMapping.setDataFormat(DataFormat.JSON);
|
||||
ColumnMapping first = new ColumnMapping("rownumber", "int");
|
||||
|
@ -195,7 +210,7 @@ class E2ETest {
|
|||
ingestionPropertiesWithColumnMapping.setIngestionMapping(columnMapping, IngestionMappingKind.JSON);
|
||||
ingestionPropertiesWithColumnMapping.setDataFormat(DataFormat.JSON);
|
||||
|
||||
IngestionProperties ingestionPropertiesWithTableReportMethod = new IngestionProperties(databaseName, tableName);
|
||||
IngestionProperties ingestionPropertiesWithTableReportMethod = new IngestionProperties(DB_NAME, tableName);
|
||||
ingestionPropertiesWithTableReportMethod.setFlushImmediately(true);
|
||||
ingestionPropertiesWithTableReportMethod.setDataFormat(DataFormat.CSV);
|
||||
ingestionPropertiesWithTableReportMethod.setReportMethod(IngestionProperties.IngestionReportMethod.TABLE);
|
||||
|
@ -267,7 +282,7 @@ class E2ETest {
|
|||
Thread.sleep(i * 100);
|
||||
|
||||
if (checkViaJson) {
|
||||
String result = queryClient.executeToJsonResult(databaseName, String.format("%s | count", tableName));
|
||||
String result = queryClient.executeToJsonResult(DB_NAME, String.format("%s | count", tableName));
|
||||
JsonNode jsonNode = objectMapper.readTree(result);
|
||||
ArrayNode jsonArray = jsonNode.isArray() ? (ArrayNode) jsonNode : null;
|
||||
JsonNode primaryResult = null;
|
||||
|
@ -281,7 +296,7 @@ class E2ETest {
|
|||
Assertions.assertNotNull(primaryResult, "Primary result cant be null since we need the row count");
|
||||
actualRowsCount = (primaryResult.get("Rows")).get(0).get(0).asInt() - currentCount;
|
||||
} else {
|
||||
KustoOperationResult result = queryClient.execute(databaseName, String.format("%s | count", tableName));
|
||||
KustoOperationResult result = queryClient.executeQuery(DB_NAME, String.format("%s | count", tableName));
|
||||
KustoResultSetTable mainTableResult = result.getPrimaryResults();
|
||||
mainTableResult.next();
|
||||
actualRowsCount = mainTableResult.getInt(0) - currentCount;
|
||||
|
@ -307,13 +322,16 @@ class E2ETest {
|
|||
|
||||
private boolean isDatabasePrincipal(Client localQueryClient) {
|
||||
KustoOperationResult result = null;
|
||||
boolean found = false;
|
||||
try {
|
||||
result = localQueryClient.execute(databaseName, String.format(".show database %s principals", databaseName));
|
||||
// result = localQueryClient.execute(databaseName, String.format(".show version"));
|
||||
result = localQueryClient.executeMgmt(DB_NAME, String.format(".show database %s principals", DB_NAME));
|
||||
} catch (Exception ex) {
|
||||
Assertions.fail("Failed to execute show database principals command", ex);
|
||||
Assertions.fail("Failed to execute show database principals command.", ex);
|
||||
}
|
||||
return resultContainsPrincipal(result);
|
||||
}
|
||||
|
||||
private boolean resultContainsPrincipal(KustoOperationResult result) {
|
||||
boolean found = false;
|
||||
KustoResultSetTable mainTableResultSet = result.getPrimaryResults();
|
||||
while (mainTableResultSet.next()) {
|
||||
if (mainTableResultSet.getString("PrincipalFQN").equals(principalFqn)) {
|
||||
|
@ -439,72 +457,68 @@ class E2ETest {
|
|||
@Test
|
||||
void testCreateWithUserPrompt() {
|
||||
Assumptions.assumeTrue(isManualExecution());
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithUserPrompt(System.getenv("ENGINE_CONNECTION_STRING"), null,
|
||||
System.getenv("USERNAME_HINT"));
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithUserPrompt(ENG_CONN_STR, null, UNAME_HINT);
|
||||
assertTrue(canAuthenticate(engineCsb), "Is USERNAME_HINT set to your email in your environment variables?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateWithConnectionStringAndUserPrompt() {
|
||||
Assumptions.assumeTrue(isManualExecution());
|
||||
ConnectionStringBuilder engineCsb = new ConnectionStringBuilder(
|
||||
"Data Source=" + System.getenv("ENGINE_CONNECTION_STRING") + ";User ID=" + System.getenv("USERNAME_HINT"));
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
ConnectionStringBuilder engineCsb = new ConnectionStringBuilder("Data Source=" + ENG_CONN_STR + ";User ID=" + UNAME_HINT);
|
||||
assertTrue(canAuthenticate(engineCsb), "Is USERNAME_HINT set to your email in your environment variables?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateWithDeviceAuthentication() {
|
||||
Assumptions.assumeTrue(isManualExecution());
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithDeviceCode(System.getenv("ENGINE_CONNECTION_STRING"), null);
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithDeviceCode(ENG_CONN_STR, null);
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateWithAadApplicationCertificate() throws GeneralSecurityException, IOException {
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(System.getenv("PUBLIC_X509CER_FILE_LOC")));
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(System.getenv("PRIVATE_PKCS8_FILE_LOC")));
|
||||
X509Certificate cer = SecurityUtils.getPublicCertificate(System.getenv("PUBLIC_X509CER_FILE_LOC"));
|
||||
PrivateKey privateKey = SecurityUtils.getPrivateKey(System.getenv("PRIVATE_PKCS8_FILE_LOC"));
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadApplicationCertificate(System.getenv("ENGINE_CONNECTION_STRING"), appId, cer,
|
||||
privateKey, "microsoft.onmicrosoft.com");
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(PUBLIC_X509CER_FILE_LOC));
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(PRIVATE_PKCS8_FILE_LOC));
|
||||
X509Certificate cer = SecurityUtils.getPublicCertificate(PUBLIC_X509CER_FILE_LOC);
|
||||
PrivateKey privateKey = SecurityUtils.getPrivateKey(PRIVATE_PKCS8_FILE_LOC);
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadApplicationCertificate(
|
||||
ENG_CONN_STR, APP_ID, cer, privateKey, "microsoft.onmicrosoft.com");
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateWithAadApplicationCredentials() {
|
||||
Assumptions.assumeTrue(appKey != null);
|
||||
ConnectionStringBuilder engineCsb = createConnection(System.getenv("ENGINE_CONNECTION_STRING"));
|
||||
Assumptions.assumeTrue(APP_KEY != null);
|
||||
ConnectionStringBuilder engineCsb = createConnection(ENG_CONN_STR);
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateWithConnectionStringAndAadApplicationCredentials() {
|
||||
Assumptions.assumeTrue(appKey != null);
|
||||
Assumptions.assumeTrue(APP_KEY != null);
|
||||
ConnectionStringBuilder engineCsb = new ConnectionStringBuilder(
|
||||
"Data Source=" + System.getenv("ENGINE_CONNECTION_STRING") + ";AppClientId=" + appId + ";AppKey=" + appKey + ";Authority ID=" + tenantId);
|
||||
"Data Source=" + ENG_CONN_STR + ";AppClientId=" + APP_ID + ";AppKey=" + APP_KEY + ";Authority ID=" + TENANT_ID);
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateWithAadAccessTokenAuthentication() {
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(System.getenv("TOKEN")));
|
||||
String token = System.getenv("TOKEN");
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadAccessTokenAuthentication(System.getenv("ENGINE_CONNECTION_STRING"), token);
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(TOKEN));
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadAccessTokenAuthentication(ENG_CONN_STR, TOKEN);
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateWithAadTokenProviderAuthentication() {
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(System.getenv("TOKEN")));
|
||||
Callable<String> tokenProviderCallable = () -> System.getenv("TOKEN");
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadTokenProviderAuthentication(System.getenv("ENGINE_CONNECTION_STRING"),
|
||||
tokenProviderCallable);
|
||||
Assumptions.assumeTrue(StringUtils.isNotBlank(TOKEN));
|
||||
Callable<String> tokenProviderCallable = () -> TOKEN;
|
||||
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadTokenProviderAuthentication(ENG_CONN_STR, tokenProviderCallable);
|
||||
assertTrue(canAuthenticate(engineCsb));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCloudInfoWithCluster() throws DataServiceException {
|
||||
String clusterUrl = System.getenv("ENGINE_CONNECTION_STRING");
|
||||
String clusterUrl = ENG_CONN_STR;
|
||||
CloudInfo cloudInfo = CloudInfo.retrieveCloudInfoForCluster(clusterUrl);
|
||||
assertNotSame(CloudInfo.DEFAULT_CLOUD, cloudInfo);
|
||||
assertNotNull(cloudInfo);
|
||||
|
@ -519,7 +533,7 @@ class E2ETest {
|
|||
|
||||
@Test
|
||||
void testParameterizedQuery() throws DataServiceException, DataClientException {
|
||||
IngestionProperties ingestionPropertiesWithoutMapping = new IngestionProperties(databaseName, tableName);
|
||||
IngestionProperties ingestionPropertiesWithoutMapping = new IngestionProperties(DB_NAME, tableName);
|
||||
ingestionPropertiesWithoutMapping.setFlushImmediately(true);
|
||||
ingestionPropertiesWithoutMapping.setDataFormat(DataFormat.CSV);
|
||||
|
||||
|
@ -551,7 +565,7 @@ class E2ETest {
|
|||
String query = String.format(
|
||||
"declare query_parameters(xdoubleParam:real, xboolParam:bool, xint16Param:int, xint64Param:long, xdateParam:datetime, xtextParam:string, xtimeParam:time); %s | where xdouble == xdoubleParam and xbool == xboolParam and xint16 == xint16Param and xint64 == xint64Param and xdate == xdateParam and xtext == xtextParam and xtime == xtimeParam",
|
||||
tableName);
|
||||
KustoOperationResult resultObj = queryClient.execute(databaseName, query, crp);
|
||||
KustoOperationResult resultObj = queryClient.executeQuery(DB_NAME, query, crp);
|
||||
KustoResultSetTable mainTableResult = resultObj.getPrimaryResults();
|
||||
mainTableResult.next();
|
||||
String results = mainTableResult.getString(13);
|
||||
|
@ -566,7 +580,7 @@ class E2ETest {
|
|||
|
||||
// Standard approach - API converts json to KustoOperationResult
|
||||
stopWatch.start();
|
||||
KustoOperationResult resultObj = queryClient.execute(databaseName, query, clientRequestProperties);
|
||||
KustoOperationResult resultObj = queryClient.executeQuery(DB_NAME, query, clientRequestProperties);
|
||||
stopWatch.stop();
|
||||
long timeConvertedToJavaObj = stopWatch.getTime();
|
||||
System.out.printf("Convert json to KustoOperationResult result count='%s' returned in '%s'ms%n", resultObj.getPrimaryResults().count(),
|
||||
|
@ -575,7 +589,7 @@ class E2ETest {
|
|||
// Specialized use case - API returns raw json for performance
|
||||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
String jsonResult = queryClient.executeToJsonResult(databaseName, query, clientRequestProperties);
|
||||
String jsonResult = queryClient.executeToJsonResult(DB_NAME, query, clientRequestProperties);
|
||||
stopWatch.stop();
|
||||
long timeRawJson = stopWatch.getTime();
|
||||
System.out.printf("Raw json result size='%s' returned in '%s'ms%n", jsonResult.length(), timeRawJson);
|
||||
|
@ -587,7 +601,7 @@ class E2ETest {
|
|||
stopWatch.reset();
|
||||
stopWatch.start();
|
||||
// The InputStream *must* be closed by the caller to prevent memory leaks
|
||||
try (InputStream is = streamingClient.executeStreamingQuery(databaseName, query, clientRequestProperties);
|
||||
try (InputStream is = streamingClient.executeStreamingQuery(DB_NAME, query, clientRequestProperties);
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
|
||||
StringBuilder streamedResult = new StringBuilder();
|
||||
char[] buffer = new char[65536];
|
||||
|
@ -612,19 +626,21 @@ class E2ETest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testSameHttpClientInstance() throws DataClientException, DataServiceException, URISyntaxException, IOException {
|
||||
ConnectionStringBuilder engineCsb = createConnection(System.getenv("ENGINE_CONNECTION_STRING"));
|
||||
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
|
||||
CloseableHttpClient httpClientSpy = Mockito.spy(httpClient);
|
||||
void testSameHttpClientInstance() throws DataClientException, DataServiceException, URISyntaxException {
|
||||
ConnectionStringBuilder engineCsb = createConnection(ENG_CONN_STR);
|
||||
HttpClient httpClient = HttpClientFactory.create(null);
|
||||
HttpClient httpClientSpy = Mockito.spy(httpClient);
|
||||
Client clientImpl = ClientFactory.createClient(engineCsb, httpClientSpy);
|
||||
|
||||
ClientRequestProperties clientRequestProperties = new ClientRequestProperties();
|
||||
String query = tableName + " | take 1000";
|
||||
|
||||
clientImpl.execute(databaseName, query, clientRequestProperties);
|
||||
clientImpl.execute(databaseName, query, clientRequestProperties);
|
||||
clientImpl.executeQuery(DB_NAME, query, clientRequestProperties);
|
||||
clientImpl.executeQuery(DB_NAME, query, clientRequestProperties);
|
||||
|
||||
try (HttpResponse httpResponse = Mockito.verify(httpClientSpy, atLeast(2)).sendSync(any(), eq(Context.NONE))) {
|
||||
}
|
||||
|
||||
Mockito.verify(httpClientSpy, atLeast(2)).execute(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -632,10 +648,11 @@ class E2ETest {
|
|||
KustoTrustedEndpoints.addTrustedHosts(Collections.singletonList(new MatchRule("statusreturner.azurewebsites.net", false)), false);
|
||||
List<Integer> redirectCodes = Arrays.asList(301, 302, 307, 308);
|
||||
redirectCodes.parallelStream().map(code -> {
|
||||
try (Client client = ClientFactory.createClient(
|
||||
ConnectionStringBuilder.createWithAadAccessTokenAuthentication("https://statusreturner.azurewebsites.net/nocloud/" + code, "token"))) {
|
||||
try {
|
||||
Client client = ClientFactory.createClient(
|
||||
ConnectionStringBuilder.createWithAadAccessTokenAuthentication("https://statusreturner.azurewebsites.net/nocloud/" + code, "token"));
|
||||
try {
|
||||
client.execute("db", "table");
|
||||
client.executeQuery("db", "table");
|
||||
Assertions.fail("Expected exception");
|
||||
} catch (DataServiceException e) {
|
||||
Assertions.assertTrue(e.getMessage().contains("" + code));
|
||||
|
@ -657,17 +674,20 @@ class E2ETest {
|
|||
KustoTrustedEndpoints.addTrustedHosts(Collections.singletonList(new MatchRule("statusreturner.azurewebsites.net", false)), false);
|
||||
List<Integer> redirectCodes = Arrays.asList(301, 302, 307, 308);
|
||||
redirectCodes.parallelStream().map(code -> {
|
||||
try (Client client = ClientFactory.createClient(
|
||||
ConnectionStringBuilder.createWithAadAccessTokenAuthentication("https://statusreturner.azurewebsites.net/" + code, "token"))) {
|
||||
try {
|
||||
Client client = ClientFactory.createClient(
|
||||
ConnectionStringBuilder.createWithAadAccessTokenAuthentication("https://statusreturner.azurewebsites.net/" + code, "token"));
|
||||
try {
|
||||
client.execute("db", "table");
|
||||
client.executeQuery("db", "table");
|
||||
Assertions.fail("Expected exception");
|
||||
} catch (DataServiceException e) {
|
||||
Assertions.assertTrue(e.getMessage().contains("" + code));
|
||||
Assertions.assertFalse(e.getMessage().contains("metadata"));
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}).forEach(e -> {
|
||||
|
@ -704,22 +724,4 @@ class E2ETest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProxyPlanner() throws URISyntaxException {
|
||||
String[] excludedPrefixes = new String[] {
|
||||
new URI(engineCsb.getClusterUrl()).getHost(),
|
||||
"login.microsoftonline.com"
|
||||
};
|
||||
|
||||
HttpClientProperties providedProperties = HttpClientProperties.builder()
|
||||
.routePlanner(new SimpleProxyPlanner("localhost", 8080, "http", excludedPrefixes))
|
||||
.build();
|
||||
try (Client client = ClientFactory.createClient(engineCsb, providedProperties)) {
|
||||
KustoOperationResult execute = client.execute(".show version");
|
||||
} catch (Exception e) {
|
||||
Assertions.fail(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import com.azure.data.tables.models.TableEntity;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import com.microsoft.azure.kusto.ingest.IngestionProperties.DataFormat;
|
||||
import com.microsoft.azure.kusto.ingest.exceptions.IngestionClientException;
|
||||
|
@ -64,7 +63,7 @@ class QueuedIngestClientTest {
|
|||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUpEach() throws IngestionServiceException, IngestionClientException {
|
||||
void setUpEach() throws IngestionServiceException {
|
||||
doReturn(Collections.singletonList(TestUtils.containerWithSasFromContainerName("storage")),
|
||||
Collections.singletonList(TestUtils.containerWithSasFromContainerName("storage2"))).when(resourceManagerMock)
|
||||
.getShuffledContainers();
|
||||
|
@ -378,6 +377,6 @@ class QueuedIngestClientTest {
|
|||
}
|
||||
|
||||
private String getSampleResultSetDump() {
|
||||
return System.getProperty("line.separator").equals("\n") ? "1,leo\n2,yui\n" : "1,leo\r\n2,yui\r\n";
|
||||
return System.lineSeparator().equals("\n") ? "1,leo\n2,yui\n" : "1,leo\r\n2,yui\r\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,10 +47,10 @@ class ResourceManagerTest {
|
|||
@BeforeAll
|
||||
static void setUp() throws DataClientException, DataServiceException {
|
||||
// Using answer so that we get a new result set with reset iterator
|
||||
when(clientMock.execute(Commands.INGESTION_RESOURCES_SHOW_COMMAND))
|
||||
when(clientMock.executeMgmt(Commands.INGESTION_RESOURCES_SHOW_COMMAND))
|
||||
.thenAnswer(invocationOnMock -> generateIngestionResourcesResult());
|
||||
|
||||
when(clientMock.execute(Commands.IDENTITY_GET_COMMAND))
|
||||
when(clientMock.executeMgmt(Commands.IDENTITY_GET_COMMAND))
|
||||
.thenAnswer(invocationOnMock -> generateIngestionAuthTokenResult());
|
||||
|
||||
setUpStorageResources(0);
|
||||
|
@ -195,10 +195,10 @@ class ResourceManagerTest {
|
|||
throws IngestionServiceException, DataServiceException, DataClientException {
|
||||
long waitTime = 1000;
|
||||
Client clientMockLocal = mock(Client.class);
|
||||
when(clientMockLocal.execute(Commands.INGESTION_RESOURCES_SHOW_COMMAND))
|
||||
when(clientMockLocal.executeMgmt(Commands.INGESTION_RESOURCES_SHOW_COMMAND))
|
||||
.thenAnswer(invocationOnMock -> generateIngestionResourcesResult());
|
||||
|
||||
when(clientMockLocal.execute(Commands.IDENTITY_GET_COMMAND))
|
||||
when(clientMockLocal.executeMgmt(Commands.IDENTITY_GET_COMMAND))
|
||||
.thenAnswer(invocationOnMock -> generateIngestionAuthTokenResult());
|
||||
|
||||
ResourceManager resourceManagerWithLowRefresh = new ResourceManager(clientMockLocal, waitTime, waitTime, null);
|
||||
|
@ -225,7 +225,7 @@ class ResourceManagerTest {
|
|||
public boolean shouldFail;
|
||||
}
|
||||
final Fail fail = new Fail();
|
||||
when(clientMockFail.execute(Commands.INGESTION_RESOURCES_SHOW_COMMAND))
|
||||
when(clientMockFail.executeMgmt(Commands.INGESTION_RESOURCES_SHOW_COMMAND))
|
||||
.thenAnswer(invocationOnMock -> {
|
||||
if (!fail.shouldFail) {
|
||||
return generateIngestionResourcesResult();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import com.microsoft.azure.kusto.data.BaseClient;
|
||||
import com.microsoft.azure.kusto.data.Client;
|
||||
import com.microsoft.azure.kusto.data.KustoOperationResult;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
|
||||
import com.microsoft.azure.kusto.data.exceptions.KustoServiceQueryError;
|
||||
import com.microsoft.azure.kusto.data.http.HttpPostUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
|
@ -13,6 +13,7 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.microsoft.azure.kusto.ingest.ResourceManagerTest.generateIngestionAuthTokenResult;
|
||||
import static com.microsoft.azure.kusto.ingest.ResourceManagerTest.generateIngestionResourcesResult;
|
||||
|
@ -21,19 +22,17 @@ import static org.mockito.Mockito.mock;
|
|||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ResourceManagerTimerTest {
|
||||
|
||||
@Test
|
||||
void timerTest() throws DataClientException, DataServiceException, InterruptedException, KustoServiceQueryError, IOException {
|
||||
Client mockedClient = mock(Client.class);
|
||||
final List<Date> refreshTimestamps = new ArrayList<>();
|
||||
class BooleanHolder {
|
||||
boolean gotHere = false;
|
||||
}
|
||||
BooleanHolder booleanHolder = new BooleanHolder();
|
||||
when(mockedClient.execute(Commands.IDENTITY_GET_COMMAND))
|
||||
AtomicBoolean gotHere = new AtomicBoolean(false);
|
||||
when(mockedClient.executeMgmt(Commands.IDENTITY_GET_COMMAND))
|
||||
.thenReturn(generateIngestionAuthTokenResult());
|
||||
when(mockedClient.execute(Commands.INGESTION_RESOURCES_SHOW_COMMAND)).then((Answer<KustoOperationResult>) invocationOnMock -> {
|
||||
when(mockedClient.executeMgmt(Commands.INGESTION_RESOURCES_SHOW_COMMAND)).then((Answer<KustoOperationResult>) invocationOnMock -> {
|
||||
refreshTimestamps.add((new Date()));
|
||||
booleanHolder.gotHere = true;
|
||||
gotHere.set(true);
|
||||
if (refreshTimestamps.size() == 2) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
@ -46,7 +45,7 @@ class ResourceManagerTimerTest {
|
|||
assertTrue(resourceManager.refreshIngestionAuthTokenTask.refreshedAtLeastOnce.isEmpty());
|
||||
|
||||
int runtime = 0;
|
||||
while (!booleanHolder.gotHere && runtime < 5000) {
|
||||
while (!gotHere.get() && runtime < 5000) {
|
||||
Thread.sleep(100);
|
||||
runtime += 100;
|
||||
}
|
||||
|
@ -65,21 +64,19 @@ class ResourceManagerTimerTest {
|
|||
void timerTestFailureGettingResources() throws DataClientException, DataServiceException, InterruptedException {
|
||||
Client mockedClient = mock(Client.class);
|
||||
final List<Date> refreshTimestamps = new ArrayList<>();
|
||||
class BooleanHolder {
|
||||
boolean gotHere = false;
|
||||
}
|
||||
BooleanHolder booleanHolder = new BooleanHolder();
|
||||
when(mockedClient.execute(Commands.IDENTITY_GET_COMMAND))
|
||||
.thenThrow(HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", null, new Exception(), "error"));
|
||||
when(mockedClient.execute(Commands.INGESTION_RESOURCES_SHOW_COMMAND)).then((Answer<KustoOperationResult>) invocationOnMock -> {
|
||||
refreshTimestamps.add((new Date()));
|
||||
booleanHolder.gotHere = true;
|
||||
throw HttpPostUtils.createExceptionFromResponse("https://sample.kusto.windows.net", null, new Exception(), "error");
|
||||
});
|
||||
AtomicBoolean gotHere = new AtomicBoolean(false);
|
||||
when(mockedClient.executeMgmt(Commands.IDENTITY_GET_COMMAND))
|
||||
.thenThrow(BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", null, new Exception(), "error"));
|
||||
when(mockedClient.executeMgmt(Commands.INGESTION_RESOURCES_SHOW_COMMAND))
|
||||
.then((Answer<KustoOperationResult>) invocationOnMock -> {
|
||||
refreshTimestamps.add((new Date()));
|
||||
gotHere.set(true);
|
||||
throw BaseClient.createExceptionFromResponse("https://sample.kusto.windows.net", null, new Exception(), "error");
|
||||
});
|
||||
|
||||
ResourceManager resourceManager = new ResourceManager(mockedClient, 1000L, 500L, null);
|
||||
int runtime = 0;
|
||||
while (!booleanHolder.gotHere && runtime < 5000) {
|
||||
while (!gotHere.get() && runtime < 5000) {
|
||||
Thread.sleep(100);
|
||||
runtime += 100;
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package com.microsoft.azure.kusto.ingest;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.impl.conn.DefaultRoutePlanner;
|
||||
import org.apache.http.impl.conn.DefaultSchemePortResolver;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
// Simple implementation of DefaultRoutePlanner that checks if the target url hostname is prefixed by given value
|
||||
public class SimpleProxyPlanner extends DefaultRoutePlanner {
|
||||
private HttpHost proxy;
|
||||
private String[] nonProxyHostsPrefixes;
|
||||
|
||||
public SimpleProxyPlanner(String proxyHost, int proxyPort, String scheme, String[] nonProxyHostsPrefixes) {
|
||||
super(DefaultSchemePortResolver.INSTANCE);
|
||||
proxy = new HttpHost(proxyHost, proxyPort, scheme);
|
||||
this.nonProxyHostsPrefixes = nonProxyHostsPrefixes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpHost determineProxy(
|
||||
final HttpHost target,
|
||||
final HttpRequest request,
|
||||
final HttpContext context) {
|
||||
|
||||
if (nonProxyHostsPrefixes != null) {
|
||||
for (String nonProxyHostsPrefix : nonProxyHostsPrefixes) {
|
||||
if (target.getHostName().startsWith(nonProxyHostsPrefix)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return proxy;
|
||||
}
|
||||
}
|
10
pom.xml
10
pom.xml
|
@ -32,7 +32,7 @@
|
|||
</developers>
|
||||
|
||||
<properties>
|
||||
<revision>5.2.0</revision> <!-- CHANGE THIS to adjust project version-->
|
||||
<revision>6.0.0</revision> <!-- CHANGE THIS to adjust project version-->
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<azure-bom-version>1.2.25</azure-bom-version>
|
||||
|
@ -43,20 +43,18 @@
|
|||
<slf4j.version>1.7.36</slf4j.version>
|
||||
<commons-lang3.version>3.14.0</commons-lang3.version>
|
||||
<commons-text.version>1.11.0</commons-text.version>
|
||||
<commons-codec.version>1.16.1</commons-codec.version>
|
||||
<httpclient.version>4.5.14</httpclient.version>
|
||||
<commons-codec.version>1.17.0</commons-codec.version>
|
||||
<apache.httpclient.version>4.5.14</apache.httpclient.version>
|
||||
<httpcore.version>4.4.16</httpcore.version>
|
||||
<fasterxml.jackson.core.version>2.16.0</fasterxml.jackson.core.version>
|
||||
<reactor-core.version>3.4.34</reactor-core.version>
|
||||
<univocity-parsers.version>2.9.1</univocity-parsers.version>
|
||||
<resilience4j.version>1.7.1</resilience4j.version>
|
||||
<io.vavr.version>0.10.4</io.vavr.version>
|
||||
<!-- Test dependencies -->
|
||||
<bouncycastle.version>1.77</bouncycastle.version>
|
||||
<jsonassert.version>1.5.0</jsonassert.version>
|
||||
<sqlite-jdbc.version>3.45.2.0</sqlite-jdbc.version>
|
||||
<sqlite-jdbc.version>3.45.3.0</sqlite-jdbc.version>
|
||||
<annotations.version>24.1.0</annotations.version>
|
||||
|
||||
<!-- Other dependencies -->
|
||||
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||
<maven-assembly-plugin.version>3.7.1</maven-assembly-plugin.version>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</developers>
|
||||
|
||||
<properties>
|
||||
<revision>5.0.3</revision> <!-- CHANGE THIS to match project version in the root (not technically parent) pom -->
|
||||
<revision>6.0.0</revision> <!-- CHANGE THIS to match project version in the root (not technically parent) pom -->
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
|
||||
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||
|
|
|
@ -156,7 +156,7 @@ public class Utils {
|
|||
KustoOperationResult result;
|
||||
|
||||
try {
|
||||
result = kustoClient.execute(databaseName, command, clientRequestProperties);
|
||||
result = kustoClient.executeQuery(databaseName, command, clientRequestProperties);
|
||||
System.out.printf("Response from executed command '%s':%n", command);
|
||||
KustoResultSetTable primaryResults = result.getPrimaryResults();
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import com.microsoft.azure.kusto.data.ClientFactory;
|
||||
import com.microsoft.azure.kusto.data.Client;
|
||||
import com.microsoft.azure.kusto.data.ClientRequestProperties;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.KustoOperationResult;
|
||||
import com.microsoft.azure.kusto.data.KustoResultSetTable;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
|
@ -40,7 +40,7 @@ public class AdvancedQuery {
|
|||
"range x from 1 to 100 step 1",
|
||||
"| extend ts = totimespan(strcat(x,'.00:00:00'))",
|
||||
"| project timestamp = now(ts), eventName = strcat('event ', x)");
|
||||
client.execute(database, tableCommand);
|
||||
client.executeMgmt(database, tableCommand);
|
||||
|
||||
// Query for an event where the name is "event 1".
|
||||
// Utilize ClientRequestProperties to pass query parameters to protect against injection attacks.
|
||||
|
@ -51,7 +51,7 @@ public class AdvancedQuery {
|
|||
"declare query_parameters(eventNameFilter:string);",
|
||||
"Events",
|
||||
"| where eventName == eventNameFilter");
|
||||
KustoOperationResult results = client.execute(database, query, clientRequestProperties);
|
||||
KustoOperationResult results = client.executeQuery(database, query, clientRequestProperties);
|
||||
KustoResultSetTable mainTableResult = results.getPrimaryResults();
|
||||
System.out.printf("Kusto sent back %s rows.%n", mainTableResult.count());
|
||||
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
import com.azure.core.http.ProxyOptions;
|
||||
import com.microsoft.azure.kusto.data.Client;
|
||||
import com.microsoft.azure.kusto.data.ClientFactory;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.conn.routing.HttpRoute;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class Proxy {
|
||||
public static void main(String[] args) {
|
||||
// See file test\java\com\microsoft\azure\kusto\data\http\SimpleProxyPlanner for an example of a custom route planner class
|
||||
|
||||
ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP,
|
||||
InetSocketAddress.createUnresolved("host.example.com", 8080));
|
||||
|
||||
HttpClientProperties providedProperties = HttpClientProperties.builder()
|
||||
.routePlanner((host, request, context) -> {
|
||||
// Your custom route planning logic here
|
||||
return new HttpRoute(new HttpHost("example.com"));
|
||||
}).build();
|
||||
try (Client client = ClientFactory.createClient(ConnectionStringBuilder.createWithUserPrompt("https://ohadev.swedencentral.dev.kusto.windows.net"),
|
||||
providedProperties)) {
|
||||
client.execute(".show version");
|
||||
.proxy(proxyOptions)
|
||||
.build();
|
||||
try {
|
||||
Client client = ClientFactory.createClient(ConnectionStringBuilder.createWithUserPrompt(
|
||||
"https://ohadev.swedencentral.dev.kusto.windows.net"), providedProperties);
|
||||
client.executeMgmt(".show version");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import com.microsoft.azure.kusto.data.Client;
|
||||
import com.microsoft.azure.kusto.data.ClientFactory;
|
||||
import com.microsoft.azure.kusto.data.ClientRequestProperties;
|
||||
import com.microsoft.azure.kusto.data.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.http.HttpClientProperties;
|
||||
import com.microsoft.azure.kusto.data.KustoOperationResult;
|
||||
import com.microsoft.azure.kusto.data.KustoResultSetTable;
|
||||
import com.microsoft.azure.kusto.data.auth.ConnectionStringBuilder;
|
||||
|
@ -31,7 +31,7 @@ public class Query {
|
|||
|
||||
Client client = ClientFactory.createClient(csb, properties);
|
||||
|
||||
KustoOperationResult results = client.execute(".show version");
|
||||
KustoOperationResult results = client.executeQuery(".show version");
|
||||
KustoResultSetTable mainTableResult = results.getPrimaryResults();
|
||||
System.out.printf("Kusto sent back %s rows.%n", mainTableResult.count());
|
||||
|
||||
|
@ -44,7 +44,7 @@ public class Query {
|
|||
ClientRequestProperties clientRequestProperties = new ClientRequestProperties();
|
||||
clientRequestProperties.setTimeoutInMilliSec(TimeUnit.MINUTES.toMillis(1));
|
||||
|
||||
results = client.execute(System.getProperty("dbName"), System.getProperty("query"), clientRequestProperties);
|
||||
results = client.executeQuery(System.getProperty("dbName"), System.getProperty("query"), clientRequestProperties);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче