Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removes apache http libraries and replace with azure core httpclient #335

Merged
merged 55 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
73b9311
initial commit removing apache http libraries
Dec 10, 2023
9d35246
updated MSAL wrapper class to include headers in response
Dec 10, 2023
cd5a5a5
Added TestHttpResponse class to replace BasicHttpResponse in Tests
Dec 10, 2023
73754c7
Added KustoParseException to replace ParseException from old Apache C…
Dec 10, 2023
989019f
removed todo regarding ParseException
Dec 10, 2023
981a45c
removed EofSensorInputStream
Dec 10, 2023
00001b8
added fixme to replace EofSensorInputStream
Dec 10, 2023
f7b7ce5
Return of the Apache
Dec 13, 2023
2f66eb2
Fixed UtilitiesTest
Dec 13, 2023
3a61d30
return UriBuilder to UriUtils
Dec 13, 2023
18555a9
add proxy options back to HttpClientFactory
Dec 13, 2023
d7e9b2b
ran formatter
Dec 13, 2023
7b7efea
closeable and fix build
ohbitton Dec 13, 2023
3429dee
fix test
ohbitton Dec 13, 2023
5fc9830
format
ohbitton Dec 13, 2023
0cc1c22
removed extra throws
The-Funk Dec 14, 2023
d0cada0
Per Ohad review comments:
Dec 22, 2023
2ab94f3
removed caught exceptions that were rethrown with no changes/side eff…
Dec 22, 2023
245185b
removes block on body by using BinaryData
Dec 23, 2023
13d2cd6
ran formatter
Dec 23, 2023
e9a0aa6
added Tracing object and builder to shorten method signatures
Dec 23, 2023
56cb44c
puts old URIBuilder back in place
The-Funk Jan 17, 2024
83f7ff9
Merge branch 'master' into remove_apache_http_libraries
The-Funk Jan 17, 2024
c1705a7
Merge branch 'master' into remove_apache_http_libraries
The-Funk Jan 18, 2024
3591aae
update readme with ProxyOptions
The-Funk Jan 18, 2024
66bc361
aligns with PR 342
The-Funk Jan 22, 2024
60e4b1d
Merge branch 'master' into remove_apache_http_libraries
The-Funk Jan 22, 2024
e22f38c
Merge branch 'master' into remove_apache_http_libraries
The-Funk Feb 1, 2024
0698dbd
Merge branch 'master' into remove_apache_http_libraries
The-Funk Feb 2, 2024
04bb268
fix issue with Closeable
The-Funk Feb 2, 2024
0755bd2
ran formatter
The-Funk Feb 2, 2024
4ef99c9
Fix sync HttpWrapper implementation
ohbitton Feb 7, 2024
e9a9c62
Merge branch 'master' into remove_apache_http_libraries
ohadbitt Feb 7, 2024
2a37cfa
fix build
ohbitton Feb 7, 2024
16c4fe9
format
ohbitton Feb 7, 2024
e64e0ee
build
ohbitton Feb 7, 2024
495f4a6
Few bug fixes
ohbitton Feb 28, 2024
61ee771
Merge branch 'master' into remove_apache_http_libraries
The-Funk Feb 29, 2024
c25960f
readd the exclusion of commons-codec for security posture
Mar 1, 2024
abae5fb
adds response timeout and client provider to the client properties wr…
Mar 2, 2024
286bbe1
removes references to Netty
Mar 2, 2024
d9dc27c
removes extra comment
Mar 2, 2024
3349315
adds Keep-Alive back to prevent breaking change for users of Keep-Ali…
Mar 2, 2024
9370530
removes deflate algorithm from encoding header
Mar 2, 2024
350dc1b
revert removing Apache core (URIBuilder depends on utilities in core)
Mar 2, 2024
b17d8a0
remove netty from client factory
The-Funk Mar 4, 2024
df53501
Merge branch 'master' into remove_apache_http_libraries
ohadbitt Mar 7, 2024
ad12749
fix
ohbitton Mar 10, 2024
b2edfe7
Merge branch 'remove_apache_http_libraries' of https://github.com/The…
ohbitton Mar 10, 2024
767c7b5
Merge branch 'master' into remove_apache_http_libraries
ohadbitt Mar 10, 2024
54d1ec5
removes CloseParentResourcesStream.java
The-Funk Mar 11, 2024
5b6e2f7
reusable Gzip logic
Mar 14, 2024
5d3a6ed
factory now uses azure-core default http client options
Mar 14, 2024
8676c50
I always forget the formatter
The-Funk Mar 17, 2024
1f7785b
fix test
ohbitton Mar 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [6.0.0] - 2024-03-07
### 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.

## [5.0.5] - 2024-03-06
### Fixed
- Fixed bugs in how ClientRequestProperties' servertimeout is set/get, and aligned the 3 different ways to set/get this option
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 1 addition & 11 deletions data/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,27 +159,17 @@
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
<version>${apache.httpclient.version}</version>
<exclusions>
<exclusion>
<artifactId>httpcore</artifactId>
<groupId>org.apache.httpcomponents</groupId>
</exclusion>
<exclusion>
<artifactId>commons-codec</artifactId>
<groupId>org.apache.commons</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
Expand Down
167 changes: 167 additions & 0 deletions data/src/main/java/com/microsoft/azure/kusto/data/BaseClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
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.HttpRequestBuilder;
import com.microsoft.azure.kusto.data.http.HttpStatus;
import org.apache.commons.lang3.StringUtils;
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 {

// Todo: Add async version of this method

// Execute and get the response
try (HttpResponse response = httpClient.sendSync(request, Context.NONE)) {
String responseBody = Utils.isGzipResponse(response) ? Utils.gzipedInputToString(response.getBodyAsBinaryData().toStream())
: response.getBodyAsBinaryData().toString();

if (responseBody != null) {
switch (response.getStatusCode()) {
case HttpStatus.OK:
return responseBody;
case HttpStatus.TOO_MANY_REQS:
throw new ThrottleException(request.getUrl().toString());
default:
throw createExceptionFromResponse(request.getUrl().toString(), response, null, responseBody);
}
}
}

return null;
}

protected InputStream postToStreamingOutput(HttpRequest request) throws DataServiceException {
return postToStreamingOutput(request, 0);
}

protected InputStream postToStreamingOutput(HttpRequest request, int redirectCount) throws DataServiceException {

boolean returnInputStream = false;
String errorFromResponse = null;

HttpResponse httpResponse = null;
try {

// Todo: Implement async version of this method
httpResponse = httpClient.sendSync(request, Context.NONE);

int responseStatusCode = httpResponse.getStatusCode();

if (responseStatusCode == HttpStatus.OK) {
returnInputStream = true;
return Utils.getResponseAsStream(httpResponse);
}

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 caller, they won't be able to read from it
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

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;

Expand Down Expand Up @@ -43,12 +44,12 @@ public static Client createClient(ConnectionStringBuilder csb, HttpClientPropert
* 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);
}

/**
Expand Down Expand Up @@ -85,7 +86,7 @@ public static StreamingClient createStreamingClient(ConnectionStringBuilder csb,
* @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);
}
}
Loading
Loading